Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: small bootstrap module improvements #60

Merged
merged 2 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions kit/azure/bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,24 @@ cfmm:

This documentation is intended as a reference documentation for cloud foundation or platform engineers using this module.

## UPN handling for AAD Guest users
Useful if you need to translate emails into UPNs (User Principal Names) as necessary, especially for guest users.
## Terraform State Storage

This module includes configuration to set up a state backend using Azure blob storage.
You can activate this by configuring the `terraform_state_storage` variable.

Like all bootstrap modules published on collie hub, you will need to deploy this module twice to complete the bootstrap process.
Please see the [bootstrap tutorial](https://collie.cloudfoundation.org/tutorial/deploy-first-module.html#bootstrap-a-cloud-platform) for more info.

> If you're not using `terraform_state_storage`, please configure your own backend in `platform.hcl`

## Platform Engineers Group

This module sets up an AAD group for managing platform engineers. This is required in conjunction with
enabling access to terraform state storage but can also be used to grant administrative access to Azure resources.

### UPN handling for AAD Guest users

Useful if you need to translate emails into UPNs (User Principal Names) as necessary, especially for guest users.
You can add this code block to your terragrunt.hcl file instead of using inputs."

```hcl
Expand Down Expand Up @@ -71,17 +87,16 @@ upn_domain = "#EXT#@devmeshithesheep.onmicrosoft.com"
| [azuread_service_principal.msgraph](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/service_principal) | data source |
| [azuread_users.platform_engineers_members](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/users) | data source |
| [azurerm_management_group.root](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/management_group) | data source |
| [azurerm_subscription.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aad_tenant_id"></a> [aad\_tenant\_id](#input\_aad\_tenant\_id) | Id of the AAD Tenant. This is also the simultaneously the id of the root management group. | `string` | n/a | yes |
| <a name="input_file_path"></a> [file\_path](#input\_file\_path) | tfstate-config file for running the bootstrap | `string` | `"tfstates-config.yml"` | no |
| <a name="input_platform_engineers_members"></a> [platform\_engineers\_members](#input\_platform\_engineers\_members) | Platform engineers with access to this platform's terraform state | <pre>list(object({<br> email = string,<br> upn = string,<br> }))</pre> | n/a | yes |
| <a name="input_resources_cloudfoundation"></a> [resources\_cloudfoundation](#input\_resources\_cloudfoundation) | tfstate resource group for the statefiles | `string` | n/a | yes |
| <a name="input_service_principal_name"></a> [service\_principal\_name](#input\_service\_principal\_name) | name of the Service Principal for deploying the cloud foundation | `string` | `"cloudfoundation_tf_deploy_user"` | no |
| <a name="input_terraform_state_storage"></a> [terraform\_state\_storage](#input\_terraform\_state\_storage) | Configure this object to enable setting up a terraform state store in Azure Storage. | <pre>object({<br> location = string<br> })</pre> | `null` | no |
| <a name="input_platform_engineers_members"></a> [platform\_engineers\_members](#input\_platform\_engineers\_members) | Set up a group of platform engineers. If enabled, this group will receive access to terraform\_state\_storage | <pre>list(object({<br> email = string,<br> upn = string,<br> }))</pre> | n/a | yes |
| <a name="input_service_principal_name"></a> [service\_principal\_name](#input\_service\_principal\_name) | name of the Service Principal used to perform all deployments in this platform | `string` | `"cloudfoundation_tf_deploy_user"` | no |
| <a name="input_terraform_state_storage"></a> [terraform\_state\_storage](#input\_terraform\_state\_storage) | Configure this object to enable setting up a terraform state store in Azure Storage. | <pre>object({<br> location = string,<br> name = string,<br> config_file_path = string<br> })</pre> | `null` | no |

## Outputs

Expand All @@ -91,5 +106,5 @@ upn_domain = "#EXT#@devmeshithesheep.onmicrosoft.com"
| <a name="output_client_principal_id"></a> [client\_principal\_id](#output\_client\_principal\_id) | n/a |
| <a name="output_client_secret"></a> [client\_secret](#output\_client\_secret) | n/a |
| <a name="output_documentation_md"></a> [documentation\_md](#output\_documentation\_md) | n/a |
| <a name="output_resources_cloudfoundation"></a> [resources\_cloudfoundation](#output\_resources\_cloudfoundation) | n/a |
| <a name="output_platform_engineers_azuread_group_id"></a> [platform\_engineers\_azuread\_group\_id](#output\_platform\_engineers\_azuread\_group\_id) | n/a |
<!-- END_TF_DOCS -->
24 changes: 22 additions & 2 deletions kit/azure/bootstrap/documentation.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
output "documentation_md" {
value = <<EOF
The Azure Service Principal deploying this cloudfoundation is named `${var.service_principal_name}`.
The credentials are stored in terraform state.

# Deployment Automation

We are using an Azure Service Principal to deploy all cloudfoundation infrastructure.
The service principal is named `${var.service_principal_name}`.

%{if var.terraform_state_storage != null}
## Terraform State Management

Terraform state for the cloud foundation repository is stored in an Azure Blob Storage Container.
This container is located in the subscription `${data.azurerm_subscription.current.display_name}`.

Access to terraform state is restricted to members of the `${azuread_group.platform_engineers.display_name}` group.
%{endif}

## Platform Engineer Access Management

The `${azuread_group.platform_engineers.display_name}` group is used to grant privileged access to members of the
cloud foundation team. The group has the following members:

${join("\n", formatlist("- %s", var.platform_engineers_members[*].email))}

EOF
}
11 changes: 7 additions & 4 deletions kit/azure/bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ data "azurerm_management_group" "root" {
name = var.aad_tenant_id
}

data "azurerm_subscription" "current" {
}

// we put the terraform_state part into its own module as that simplifies making it optional
module "terraform_state" {
count = var.terraform_state_storage != null ? 1 : 0

source = "./terraform-state"
location = var.terraform_state_storage.location
file_path = var.file_path
resources_cloudfoundation = var.resources_cloudfoundation
JohannesRudolph marked this conversation as resolved.
Show resolved Hide resolved
source = "./terraform-state"
location = var.terraform_state_storage.location
resources_cloudfoundation = var.terraform_state_storage.name
terraform_state_config_file_path = var.terraform_state_storage.config_file_path
}

# Set permissions on the blob store
Expand Down
4 changes: 2 additions & 2 deletions kit/azure/bootstrap/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ output "client_principal_id" {
value = azuread_service_principal.cloudfoundation_deploy.id
}

output "resources_cloudfoundation" {
value = var.resources_cloudfoundation
output "platform_engineers_azuread_group_id" {
value = azuread_group.platform_engineers.id
}

24 changes: 11 additions & 13 deletions kit/azure/bootstrap/template/platform-module/terragrunt.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,19 @@ EOF
}

inputs = {
# for creation of the resource_group and storage container we are using the
# https://registry.terraform.io/providers/aztfmod/azurecaf/latest/docs/resources/azurecaf_naming_convention
# you only need the the name of your foundation like likvid the result would like rg-tfstate-likvid-ewt

resources_cloudfoundation = "cloudfoundation" #TODO change, name your rg fo the statefiles
service_principal_name = "cloudfoundation_tf_deploy" #TODO change, name your spn
aad_tenant_id = include.platform.locals.platform.azure.aadTenantId
service_principal_name = "cloudfoundation_tf_deploy" #TODO change, name your spn

terraform_state_storage = {
location = "germanywestcentral" #TODO change, the location where your bucket live
name = "cloudfoundation" #TODO change, used to derive names for Azure resource group and storage account name
location = "germanywestcentral" #TODO change, the azure location of the resource group and storage account
config_file_path = include.platform.locals.terraform_state_config_file_path # platform.hcl expects state configuration output in this location, do not change
}

platform_engineers_members = [{
email = "meshi@meshithesheep.io" #TODO change, enter PLATFORM ENGINEERS MAIL here
upn = "meshi@meshithesheep.onmicrosoft.com" }] #TODO change, enter PLATFORM ENGINEERS UPN here

file_path = include.platform.locals.file_path
aad_tenant_id = include.platform.locals.platform.azure.aadTenantId
platform_engineers_members = [
{
email = "meshi@meshithesheep.io" #TODO change, enter PLATFORM ENGINEERS MAIL here
upn = "meshi@meshithesheep.onmicrosoft.com" #TODO change, enter PLATFORM ENGINEERS UPN here
}
]
}
21 changes: 11 additions & 10 deletions kit/azure/bootstrap/template/platform/platform.hcl
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
locals {
# define shared configuration here that's included by all terragrunt configurations in this locals
platform = yamldecode(regex("^---([\\s\\S]*)\\n---\\n[\\s\\S]*$", file(".//README.md"))[0])
file_path = "${get_parent_terragrunt_dir()}/tfstates-config.yml"
tfstateconfig = try(yamldecode(file(local.file_path)), [])
platform = yamldecode(regex("^---([\\s\\S]*)\\n---\\n[\\s\\S]*$", file(".//README.md"))[0])

# if we use terraform_state_storage, it will generate this file here to provide backend configuration
terraform_state_config_file_path = "${get_parent_terragrunt_dir()}/tfstates-config.yml"
tfstateconfig = try(yamldecode(file(local.terraform_state_config_file_path)), null)
}

# terragrunt does not support azure remote_state, so we use a traditional generate block
generate "backend" {
path = "backend.tf"
if_exists = "overwrite"
contents = <<EOF
%{if fileexists(local.file_path)}
terraform {
terraform {
%{if local.tfstateconfig != null}
backend "azurerm" {
use_azuread_auth = true
tenant_id = "${local.platform.azure.aadTenantId}"
Expand All @@ -20,12 +22,11 @@ generate "backend" {
storage_account_name = "${try(local.tfstateconfig.storage_account_name, "")}"
container_name = "${try(local.tfstateconfig.container_name, "")}"
key = "${path_relative_to_include()}.tfstate"
}
}%{else}
terraform {
}
%{else}
backend "local" {
}
%{endif}
}
%{endif}
EOF
EOF
}
5 changes: 2 additions & 3 deletions kit/azure/bootstrap/terraform-state/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ resource "azurerm_storage_container" "tfstates" {
}

resource "local_file" "tfstates_yaml" {
filename = var.file_path
filename = var.terraform_state_config_file_path
content = <<-EOT
storage_account_name: ${azurecaf_name.cafrandom_st.result}
container_name: ${azurerm_storage_container.tfstates.name}
location: ${azurerm_storage_account.tfstates.location}
resource_group_name: ${azurecaf_name.cafrandom_rg.result}

EOT
EOT
}
2 changes: 1 addition & 1 deletion kit/azure/bootstrap/terraform-state/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ variable "location" {
description = "Azure location for deploying the storage account"
}

variable "file_path" {
variable "terraform_state_config_file_path" {
type = string
nullable = false
description = "tfstate-config file for running the bootstrap"
Expand Down
20 changes: 5 additions & 15 deletions kit/azure/bootstrap/variables.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
variable "service_principal_name" {
type = string
description = "name of the Service Principal for deploying the cloud foundation"
description = "name of the Service Principal used to perform all deployments in this platform"
default = "cloudfoundation_tf_deploy_user"
}

Expand All @@ -11,29 +11,19 @@ variable "aad_tenant_id" {

variable "terraform_state_storage" {
type = object({
location = string
location = string,
name = string,
config_file_path = string
})
nullable = true
default = null
description = "Configure this object to enable setting up a terraform state store in Azure Storage."
}

variable "platform_engineers_members" {
description = "Platform engineers with access to this platform's terraform state"
description = "Set up a group of platform engineers. If enabled, this group will receive access to terraform_state_storage"
type = list(object({
email = string,
upn = string,
}))
}

variable "file_path" {
type = string
default = "tfstates-config.yml"
description = "tfstate-config file for running the bootstrap"
}

variable "resources_cloudfoundation" {
type = string
nullable = false
description = "tfstate resource group for the statefiles"
}
Loading