diff --git a/Makefile b/Makefile index def683f..d090076 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ docs: terraform-docs --lockfile=false ./modules/base terraform-docs --config docs/.terraform-docs.yaml . terraform-docs --config docs/.terraform-docs-example.yaml . + terraform-docs --config docs/.terraform-docs.yaml ./examples/with-backstage + terraform-docs --config docs/.terraform-docs-example.yaml ./examples/with-backstage # Format all terraform files fmt: diff --git a/README.md b/README.md index 76e961b..c340ba4 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,12 @@ Once you are finished with the reference architecture, you can remove all provis | location | Azure region to deploy into | `string` | n/a | yes | | subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | | vm\_size | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | + +### Outputs + +| Name | Description | +|------|-------------| +| aks\_cluster\_issuer\_url | Issuer URL for the OpenID Connect discovery endpoint | ## Learn more diff --git a/examples/with-backstage/README.md b/examples/with-backstage/README.md index 1a29fdf..18690fb 100644 --- a/examples/with-backstage/README.md +++ b/examples/with-backstage/README.md @@ -85,15 +85,19 @@ Once you are finished with the reference architecture, you can remove all provis | Name | Version | |------|---------| | terraform | >= 1.3.0 | -| Azure | ~> 5.17 | +| azapi | ~> 1.11 | +| azuread | ~> 2.47 | +| azurerm | ~> 3.87 | | github | ~> 5.38 | +| helm | ~> 2.12 | | humanitec | ~> 1.0 | +| kubernetes | ~> 2.25 | ### Providers | Name | Version | |------|---------| -| Azure | ~> 5.17 | +| azurerm | ~> 3.87 | | github | ~> 5.38 | | humanitec | ~> 1.0 | @@ -101,36 +105,29 @@ Once you are finished with the reference architecture, you can remove all provis | Name | Source | Version | |------|--------|---------| -| backstage\_ecr | terraform-Azure-modules/ecr/Azure | ~> 1.6 | -| backstage\_iam\_policy\_ecr\_create\_repository | git:: | n/a | -| backstage\_iam\_role\_service\_account | git:: | n/a | -| backstage\_k8s\_service\_account | git:: | n/a | -| backstage\_mysql | git:: | n/a | -| backstage\_postgres | git:: | n/a | -| backstage\_workload | git:: | n/a | +| backstage\_mysql | git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/mysql/basic | main | +| backstage\_postgres | git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/postgres/basic | main | | base | ../../modules/base | n/a | -| iam\_github\_oidc\_provider | terraform-Azure-modules/iam/Azure//modules/iam-github-oidc-provider | ~> 5.30 | -| iam\_github\_oidc\_role | terraform-Azure-modules/iam/Azure//modules/iam-github-oidc-role | ~> 5.30 | ### Resources | Name | Type | |------|------| -| [Azure_iam_policy.ecr_push_policy](https://registry.terraform.io/providers/hashicorp/Azure/latest/docs/resources/iam_policy) | resource | +| [azurerm_federated_identity_credential.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/federated_identity_credential) | resource | +| [azurerm_role_assignment.github_oidc_identity_acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | +| [azurerm_user_assigned_identity.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | resource | | [github_actions_organization_secret.backstage_humanitec_token](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_secret) | resource | -| [github_actions_organization_variable.backstage_Azure_region](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | -| [github_actions_organization_variable.backstage_Azure_role_arn](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_acr_name](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_client_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_subscription_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_tenant_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | | [github_actions_organization_variable.backstage_cloud_provider](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | | [github_actions_organization_variable.backstage_humanitec_org_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_repository_oidc_subject_claim_customization_template.backstage](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_repository_oidc_subject_claim_customization_template) | resource | | [github_repository.backstage](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) | resource | | [humanitec_application.backstage](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/application) | resource | -| [humanitec_resource_definition_criteria.backstage_iam_policy_ecr_create_repository](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | -| [humanitec_resource_definition_criteria.backstage_iam_role_service_account](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | -| [humanitec_resource_definition_criteria.backstage_k8s_service_account](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | | [humanitec_resource_definition_criteria.backstage_mysql](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | | [humanitec_resource_definition_criteria.backstage_postgres](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | -| [humanitec_resource_definition_criteria.backstage_workload](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | -| [humanitec_value.Azure_default_region](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | | [humanitec_value.backstage_cloud_provider](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | | [humanitec_value.backstage_github_app_client_id](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | | [humanitec_value.backstage_github_app_client_secret](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | @@ -145,12 +142,16 @@ Once you are finished with the reference architecture, you can remove all provis | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| Azure\_account\_id | Azure Account (ID) to use | `string` | n/a | yes | -| Azure\_region | Azure region | `string` | n/a | yes | | github\_org\_id | GitHub org id | `string` | n/a | yes | | humanitec\_ci\_service\_user\_token | Humanitec CI Service User Token | `string` | n/a | yes | | humanitec\_org\_id | Humanitec Organization ID | `string` | n/a | yes | -| disk\_size | Disk size in GB to use for EKS nodes | `number` | `20` | no | -| instance\_types | List of EC2 instances types to use for EKS nodes | `list(string)` |
[
"t3.large"
]
| no | -| resource\_packs\_Azure\_rev | Revision of the resource-packs-Azure repository to use | `string` | `"refs/heads/main"` | no | +| location | Azure region to deploy into | `string` | n/a | yes | +| subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | +| vm\_size | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | + +### Outputs + +| Name | Description | +|------|-------------| +| aks\_cluster\_issuer\_url | Issuer URL for the OpenID Connect discovery endpoint | diff --git a/examples/with-backstage/outputs.tf b/examples/with-backstage/outputs.tf new file mode 100644 index 0000000..c73f19c --- /dev/null +++ b/examples/with-backstage/outputs.tf @@ -0,0 +1,23 @@ +# output "aks_cluster_issuer_url" { +# description = "Issuer URL for the OpenID Connect discovery endpoint" +# value = module.base.aks_oidc_issuer_url +# } +# +# output "user_assigned_identity" { +# value = azurerm_user_assigned_identity.operator +# } +# output "tenant_id" { +# value = data.azurerm_subscription.current.tenant_id +# } +# +# output "client_id" { +# value = azuread_service_principal.humanitec_orchestrator_vault.client_id +# } +# output "secret_value" { +# value = nonsensitive(azuread_service_principal_password.humanitec_orchestrator_vault.value) +# } +# +# output "humanitec_orchestrator_application" { +# value = azuread_application.humanitec_orchestrator +# } +# diff --git a/examples/with-backstage/poc-humanitec-storage.tf b/examples/with-backstage/poc-humanitec-storage.tf new file mode 100644 index 0000000..94399ce --- /dev/null +++ b/examples/with-backstage/poc-humanitec-storage.tf @@ -0,0 +1,36 @@ +resource "azurerm_storage_account" "storage_account_humanitec" { + name = "humanitecplatformprod" + resource_group_name = module.base.az_resource_group_name + location = module.base.az_resource_group_location + account_tier = "Standard" # Adjust tier and replication type as needed + account_replication_type = "LRS" +} + +# Create Storage Container +resource "azurerm_storage_container" "shared_container" { + name = "shared" + storage_account_name = azurerm_storage_account.storage_account_humanitec.name +} + + +resource "humanitec_resource_definition" "shared-storage" { + driver_type = "humanitec/echo" + id = "shared-storage" + name = "shared-storage" + type = "azure-blob" + + driver_inputs = { + values_string = jsonencode({ + "account" = azurerm_storage_account.storage_account_humanitec.name, + "container" = azurerm_storage_container.shared_container.name, + "location" = module.base.az_resource_group_location + }) + } + +} + + +resource "humanitec_resource_definition_criteria" "shared-storage" { + resource_definition_id = humanitec_resource_definition.shared-storage.id +} + diff --git a/examples/with-backstage/poc-humanitec-vault-confidential.tf b/examples/with-backstage/poc-humanitec-vault-confidential.tf new file mode 100644 index 0000000..23790bc --- /dev/null +++ b/examples/with-backstage/poc-humanitec-vault-confidential.tf @@ -0,0 +1,174 @@ +# +# Connect humanitec with condifdential secrets stored in Azure Key Vault +# Humanitec Orchestrator should not have access to the vault in any ways, just the kubernetes operator should but as RO +# + +resource "azurerm_key_vault" "humanitec_poc_confidential" { + name = var.vault_name_confidential + location = module.base.az_resource_group_location + resource_group_name = data.azurerm_resource_group.main.name + + tenant_id = data.azurerm_subscription.current.tenant_id + + sku_name = "premium" + soft_delete_retention_days = 7 + enable_rbac_authorization = true + +} + +# Create a role assignment for the current user +resource "azurerm_role_assignment" "self-confidential" { + scope = azurerm_key_vault.humanitec_poc_confidential.id + principal_id = data.azurerm_client_config.current.object_id + role_definition_name = "Key Vault Administrator" +} + + +# +# Humanitec Operator should have RO access to the confidential vault +# + +# Asign a role to the managed identity used by Humanitec Operator +resource "azurerm_role_assignment" "vault-confidential-confidential-ro" { + scope = azurerm_key_vault.humanitec_poc_confidential.id + role_definition_name = data.azurerm_role_definition.kv-secret-ro.name + principal_id = azurerm_user_assigned_identity.operator.principal_id +} + + +# Register the secret store confidential with the Operator + +resource "kubernetes_manifest" "register_operator_secret_store_confidential" { + manifest = { + apiVersion = "humanitec.io/v1alpha1" + kind = "SecretStore" + metadata = { + name = var.secret_store_confidential_id + namespace = var.humanitec_operator_namespace + labels = { + "app.humanitec.io/default-store" = "false" + } + } + spec = { + azurekv = { + url = azurerm_key_vault.humanitec_poc_confidential.vault_uri + tenantID = data.azurerm_client_config.current.tenant_id + auth : {} + } + } + } + depends_on = [helm_release.humanitec_operator] + +} + + +# +# TODO: Ask Humanitec SME does the orchestrator need to have access to the vault even as RO? +# If we are just using references only the k8s should have access the Humanitec Orchestrator shouldn't + +resource "azurerm_role_assignment" "humanitec_orchestrator_vault_confidential" { + count = var.enable_orchestrator_access_confidential ? 1 : 0 + + scope = azurerm_key_vault.humanitec_poc_confidential.id + role_definition_name = data.azurerm_role_definition.kv-secret-ro.name + principal_id = azuread_service_principal.humanitec_orchestrator_vault.id + + depends_on = [azurerm_key_vault.humanitec_poc_confidential] +} + +# Register the secret store with the Platform Orchestrator + +resource "humanitec_secretstore" "humanitec_orchestrator_officer_confidential" { + id = "azurepoc-confidential" + # primary = true + azurekv = { + url = azurerm_key_vault.humanitec_poc_confidential.vault_uri + tenant_id = data.azurerm_client_config.current.tenant_id + auth = { + client_id = azuread_service_principal.humanitec_orchestrator_vault.client_id + client_secret = azuread_service_principal_password.humanitec_orchestrator_vault.value + } + } + + depends_on = [azurerm_key_vault.humanitec_poc_confidential] +} + +# create secret in the vault +resource "azurerm_key_vault_secret" "master_secret_confidential" { + name = "master-secret" + value = "secret-password-confidential-001" + key_vault_id = azurerm_key_vault.humanitec_poc_confidential.id +} + + +resource "azurerm_key_vault_secret" "mysql_username_confidential" { + name = "central-mysql-username" + value = "username-central-confidential-azure" + key_vault_id = azurerm_key_vault.humanitec_poc_confidential.id +} + +resource "azurerm_key_vault_secret" "mysql_password_confidential" { + name = "central-mysql-password" + value = "password-central-confidential-azure" + key_vault_id = azurerm_key_vault.humanitec_poc_confidential.id +} + +# Create a resource in humanitec this should be used only in production +resource "humanitec_resource_definition" "mysql-confidential" { + id = "mysql-confidential" + name = "central-confidential" + type = "mysql" + driver_type = "humanitec/echo" + + driver_inputs = { + values_string = jsonencode({ + name = "central-db1" + host = "central.mysql.database.confidential.myapp.thoughtworks.com" + user = azurerm_key_vault_secret.mysql_username_confidential.name + port = 3306 + }) + secret_refs = jsonencode({ + username = { + store = humanitec_secretstore.humanitec_orchestrator_officer_confidential.id + ref = azurerm_key_vault_secret.mysql_username_confidential.name + } + password = { + store = humanitec_secretstore.humanitec_orchestrator_officer_confidential.id + ref = azurerm_key_vault_secret.mysql_password_confidential.name + } + }) + } + + depends_on = [humanitec_secretstore.humanitec_orchestrator_officer_confidential] + +} + +resource "humanitec_resource_definition_criteria" "mysql-confidential" { + resource_definition_id = humanitec_resource_definition.mysql-confidential.id + env_type = "production" + env_id = "production" +} + +# +# Create shared secret in the vault +# +resource "azurerm_key_vault_secret" "client_api_token_confidential" { + name = "client-api-token" + value = uuid() + key_vault_id = azurerm_key_vault.humanitec_poc_confidential.id +} + + +# Create reference to the secret stored in kv +# resource "humanitec_value" "poc_shared_secret_confidential" { +# app_id = humanitec_application.demo_app.id +# key = "client-api-token" +# description = "client api token - shared secret created in Terraform " +# is_secret = true +# secret_ref = { +# store = humanitec_secretstore.humanitec_orchestration_officer_confidential.id +# ref = azurerm_key_vault_secret.client_api_token_confidential.name +# } +# } + + diff --git a/examples/with-backstage/poc-humanitec-vault.tf b/examples/with-backstage/poc-humanitec-vault.tf new file mode 100644 index 0000000..1ef0930 --- /dev/null +++ b/examples/with-backstage/poc-humanitec-vault.tf @@ -0,0 +1,272 @@ +# +# Store credentials in Azure Vault insted of Humanitec Vault +# url: https://developer.humanitec.com/integration-and-extensions/humanitec-operator/how-tos/connect-to-azure-key-vault/ +# + +resource "azurerm_key_vault" "humanitec_poc" { + name = var.vault_name + location = module.base.az_resource_group_location + resource_group_name = data.azurerm_resource_group.main.name + + tenant_id = data.azurerm_subscription.current.tenant_id + + sku_name = "premium" + soft_delete_retention_days = 7 + enable_rbac_authorization = true + +} + +# Create a role assignment for the current user +resource "azurerm_role_assignment" "self" { + scope = azurerm_key_vault.humanitec_poc.id + principal_id = data.azurerm_client_config.current.object_id + role_definition_name = "Key Vault Administrator" +} + +# Install Humanitec Operator +resource "helm_release" "humanitec_operator" { + name = "humanitec-operator" + namespace = var.humanitec_operator_namespace + repository = "oci://registry.humanitec.io/charts" + chart = "humanitec-operator" + version = "0.1.8" + + force_update = true + + create_namespace = true + + set { + name = "controllerManager.serviceAccount.annotations.azure\\.workload\\.identity/client-id" + value = azurerm_user_assigned_identity.operator.client_id + } + + set { + name = "controllerManager.podLabels.azure\\.workload\\.identity/use" + value = "true" + } + + depends_on = [ + azurerm_user_assigned_identity.operator, + azurerm_federated_identity_credential.vault + ] +} + + +# Create managed identity +resource "azurerm_user_assigned_identity" "operator" { + name = var.operator_identity + location = module.base.az_resource_group_location + resource_group_name = data.azurerm_resource_group.main.name +} + +# createa federated identity credential +resource "azurerm_federated_identity_credential" "vault" { + name = var.operator_identity + resource_group_name = module.base.az_resource_group_name + audience = ["api://AzureADTokenExchange"] + issuer = module.base.aks_oidc_issuer_url + parent_id = azurerm_user_assigned_identity.operator.id + subject = format("system:serviceaccount:%s:%s", var.humanitec_operator_namespace, var.operator_service_account_name) +} + +# Configure Key Vault access (Azure RBAC) + +# Cluster should have only read only access to tht secrets +data "azurerm_role_definition" "kv-secret-ro" { + name = "Key Vault Secrets User" +} + +data "azurerm_role_definition" "kv-secret-officer" { + name = "Key Vault Secrets Officer" +} + +# Asign a role (RO roles) to the managed identity +resource "azurerm_role_assignment" "vault-rw" { + scope = azurerm_key_vault.humanitec_poc.id + role_definition_name = data.azurerm_role_definition.kv-secret-officer.name + principal_id = azurerm_user_assigned_identity.operator.principal_id +} + +# Register the secret store with the Operator +resource "kubernetes_manifest" "register_operator" { + manifest = { + apiVersion = "humanitec.io/v1alpha1" + kind = "SecretStore" + metadata = { + name = var.secret_store_id + namespace = var.humanitec_operator_namespace + labels = { + "app.humanitec.io/default-store" = "true" + } + } + spec = { + azurekv = { + url = azurerm_key_vault.humanitec_poc.vault_uri + tenantID = data.azurerm_client_config.current.tenant_id + auth : {} + } + } + } + depends_on = [helm_release.humanitec_operator] +} + + +# Create service principal for the Operator to access the vault +# Prepare service principal credentials for the Humanitec Operator + + +resource "azuread_application" "humanitec_orchestrator_key_vault" { + display_name = "humanitec_orchestrator_key_vault" + description = "Service Principal used by Humanitec Orchestrator to access Key Vault" + owners = [data.azuread_client_config.current.object_id] +} + +resource "azuread_service_principal" "humanitec_orchestrator_vault" { + client_id = azuread_application.humanitec_orchestrator_key_vault.client_id + + alternative_names = [var.orchestrator_sp_name] + + description = "Service Principal used by Humanitec Orchestrator to access Vault" + + owners = [data.azuread_client_config.current.object_id] +} + +resource "azuread_service_principal_password" "humanitec_orchestrator_vault" { + service_principal_id = azuread_service_principal.humanitec_orchestrator_vault.object_id +} + +resource "azurerm_role_assignment" "humanitec_orchestrator_vault" { + scope = azurerm_key_vault.humanitec_poc.id + role_definition_name = data.azurerm_role_definition.kv-secret-officer.name + principal_id = azuread_service_principal.humanitec_orchestrator_vault.id + + + depends_on = [azurerm_key_vault.humanitec_poc] +} + +# Register the secret store with the Platform Orchestrator + +resource "humanitec_secretstore" "humanitec_orchestrator_officer" { + id = "azurepoc" + primary = true + azurekv = { + url = azurerm_key_vault.humanitec_poc.vault_uri + tenant_id = data.azurerm_client_config.current.tenant_id + auth = { + client_id = azuread_service_principal.humanitec_orchestrator_vault.client_id + client_secret = azuread_service_principal_password.humanitec_orchestrator_vault.value + } + } + + depends_on = [azurerm_key_vault.humanitec_poc] +} + + +# This doesn't work (terraform complains about a provider error but the creation works from the GUI) +# +# # Create reference to the secret stored in kv +# resource "humanitec_value" "poc_shared_secret" { +# app_id = humanitec_application.demo_app.id +# key = "shared_secret_001" +# description = "Shared secret created in Terraform" +# is_secret = true +# } + +# create secret in the vault +resource "azurerm_key_vault_secret" "master_secret" { + name = "master-secret" + value = "secret-password-001" + key_vault_id = azurerm_key_vault.humanitec_poc.id +} + + +resource "azurerm_key_vault_secret" "mysql_username" { + name = "central-mysql-username" + value = "username-central-azure" + key_vault_id = azurerm_key_vault.humanitec_poc.id +} + +resource "azurerm_key_vault_secret" "mysql_password" { + name = "central-mysql-password" + value = "password-central-azure" + key_vault_id = azurerm_key_vault.humanitec_poc.id +} + +# Create a resource in humanitec +resource "humanitec_resource_definition" "mysql" { + id = "echo-mysql" + name = "central" + type = "mysql" + driver_type = "humanitec/echo" + + driver_inputs = { + values_string = jsonencode({ + name = "central-db1" + host = "central.mysql.database.myapp.thoughtworks.com" + user = azurerm_key_vault_secret.mysql_username.name + port = 3306 + }) + secret_refs = jsonencode({ + username = { + store = humanitec_secretstore.humanitec_orchestrator_officer.id + ref = azurerm_key_vault_secret.mysql_username.name + } + password = { + store = humanitec_secretstore.humanitec_orchestrator_officer.id + ref = azurerm_key_vault_secret.mysql_password.name + } + }) + } + +} + +resource "humanitec_resource_definition" "cookie-config" { + id = "cookie-config" + name = "Cookie Config" + type = "config" + driver_type = "humanitec/template" + + driver_inputs = { + values_string = jsonencode({ + templates = { + cookie = "resource demo cookies" + } + }) + } +} + +resource "humanitec_resource_definition_criteria" "cookie-config" { + resource_definition_id = humanitec_resource_definition.cookie-config.id + env_type = "development" + env_id = "development" +} + +# +# Create shared secret in the vault +# + +resource "humanitec_application" "demo_app" { + id = "demo-app-terraform" + name = "Demo App created via Terraform" +} + + +resource "azurerm_key_vault_secret" "client_api_token" { + name = "client-api-token" + value = uuid() + key_vault_id = azurerm_key_vault.humanitec_poc.id +} + + +# Create reference to the secret stored in kv +resource "humanitec_value" "poc_shared_secret" { + app_id = humanitec_application.demo_app.id + key = "client-api-token" + description = "client api token - shared secret created in Terraform " + is_secret = true + secret_ref = { + store = humanitec_secretstore.humanitec_orchestrator_officer.id + ref = azurerm_key_vault_secret.client_api_token.name + } +} + diff --git a/examples/with-backstage/poc-humanitec.tf b/examples/with-backstage/poc-humanitec.tf new file mode 100644 index 0000000..2820b4b --- /dev/null +++ b/examples/with-backstage/poc-humanitec.tf @@ -0,0 +1,70 @@ +# Create default maitenance configuration for azure +resource "azurerm_maintenance_configuration" "default" { + name = "default" + resource_group_name = data.azurerm_resource_group.main.name + location = module.base.az_resource_group_location + scope = "Host" +} + +data "azurerm_resource_group" "main" { + name = module.base.az_resource_group_name +} + +resource "humanitec_environment_type" "poc-env" { + for_each = var.humanitec_envs + id = lower(each.key) + description = format("Environment: %s", each.key) +} + + +data "azurerm_subscription" "current" {} +data "azuread_client_config" "current" {} +data "azurerm_client_config" "current" {} + +# Application used by Humanitec to Access to Azure + +resource "azuread_application" "humanitec_orchestrator" { + display_name = "humanitec_orchestrator" + description = "Humanitec Orcherstrator" + owners = [data.azuread_client_config.current.object_id] +} + +resource "azuread_service_principal" "humanitec_platform" { + client_id = azuread_application.humanitec_orchestrator.client_id + + description = "Service Principal used by Humanitec Orchestrator" + app_role_assignment_required = false + + owners = [data.azuread_client_config.current.object_id] +} + +resource "time_rotating" "humanitec_platform" { + rotation_days = 7 +} + +resource "azuread_service_principal_password" "humanitec_platform" { + service_principal_id = azuread_service_principal.humanitec_platform.object_id +} + +resource "azurerm_role_assignment" "humanitec_platform" { + scope = data.azurerm_resource_group.main.id + role_definition_name = "Owner" + principal_id = azuread_service_principal.humanitec_platform.object_id +} + +resource "humanitec_resource_account" "humanitec_azure" { + type = "azure" + id = "azure-storage" + name = "azure-storage" + + credentials = jsonencode({ + "appId" : azuread_service_principal.humanitec_platform.client_id, + "displayName" : azuread_application.humanitec_orchestrator.display_name, + "password" : azuread_service_principal_password.humanitec_platform.value, + "tenant" : azuread_service_principal.humanitec_platform.application_tenant_id + }) + + depends_on = [azurerm_role_assignment.humanitec_platform] +} + + diff --git a/examples/with-backstage/provider.tf b/examples/with-backstage/provider.tf index 30712f9..bf9837c 100644 --- a/examples/with-backstage/provider.tf +++ b/examples/with-backstage/provider.tf @@ -18,15 +18,23 @@ terraform { } helm = { source = "hashicorp/helm" - version = "~> 2.12" + version = "~> 2.13" } humanitec = { source = "humanitec/humanitec" - version = "~> 1.0" + version = "~> 1.1.0" } kubernetes = { source = "hashicorp/kubernetes" - version = "~> 2.25" + version = "~> 2.27" + } + time = { + source = "hashicorp/time" + version = "0.11.1" + } + local = { + source = "hashicorp/local" + version = "2.5.1" } } required_version = ">= 1.3.0" @@ -57,6 +65,10 @@ provider "kubernetes" { cluster_ca_certificate = base64decode(module.base.aks_cluster_ca_certificate) + experiments { + manifest_resource = true + } + exec { api_version = "client.authentication.k8s.io/v1beta1" command = "kubelogin" diff --git a/examples/with-backstage/terraform.tfvars.example b/examples/with-backstage/terraform.tfvars.example new file mode 100644 index 0000000..e1a20a3 --- /dev/null +++ b/examples/with-backstage/terraform.tfvars.example @@ -0,0 +1,18 @@ + +# GitHub org id +github_org_id = "" + +# Humanitec CI Service User Token +humanitec_ci_service_user_token = "" + +# Humanitec Organization ID +humanitec_org_id = "" + +# Azure region to deploy into +location = "" + +# Azure Subscription (ID) to use +subscription_id = "" + +# The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS +vm_size = "Standard_D2_v2" \ No newline at end of file diff --git a/examples/with-backstage/variables.tf b/examples/with-backstage/variables.tf index e4c9835..28e050a 100644 --- a/examples/with-backstage/variables.tf +++ b/examples/with-backstage/variables.tf @@ -29,3 +29,58 @@ variable "vm_size" { type = string default = "Standard_D2_v2" } + +variable "humanitec_envs" { + type = set(string) + default = ["stagging", "production"] +} + +variable "vault_name" { + type = string + default = "humanitec-vault" +} + +variable "vault_name_confidential" { + type = string + default = "humanitec-vault-secret" +} + + +variable "orchestrator_sp_name" { + type = string + default = "platform-orchestrator-sp-humanitec-vault" +} + +variable "humanitec_operator_namespace" { + type = string + default = "humanitec-operator-system" +} + + +variable "secret_store_id" { + type = string + default = "azurepoc" +} + +variable "secret_store_confidential_id" { + type = string + default = "azurepoc-confidential" +} + + +variable "enable_orchestrator_access_confidential" { + description = "Enable access to the confidential vault for the orchestrator" + type = bool + default = false +} + +variable "operator_identity" { + type = string + default = "humanitec-operator-identity" +} + +variable "operator_service_account_name" { + type = string + default = "humanitec-operator-controller-manager" +} + diff --git a/modules/base/README.md b/modules/base/README.md index b1ddd97..c73aa77 100644 --- a/modules/base/README.md +++ b/modules/base/README.md @@ -74,6 +74,7 @@ Module that provides the reference architecture. |------|-------------| | [aks\_cluster\_ca\_certificate](#output\_aks\_cluster\_ca\_certificate) | Base64 encoded certificate data required to communicate with the cluster | | [aks\_host](#output\_aks\_host) | Endpoint for your Kubernetes API server | +| [aks\_oidc\_issuer\_url](#output\_aks\_oidc\_issuer\_url) | Issuer URL for the OpenID Connect discovery endpoint | | [aks\_server\_app\_id](#output\_aks\_server\_app\_id) | Azure Kubernetes Service AAD Server | | [az\_container\_registry\_id](#output\_az\_container\_registry\_id) | ID of the created azure container registry | | [az\_container\_registry\_name](#output\_az\_container\_registry\_name) | Name of the created azure container registry | diff --git a/modules/base/main.tf b/modules/base/main.tf index 3105869..3cb67ff 100644 --- a/modules/base/main.tf +++ b/modules/base/main.tf @@ -50,6 +50,8 @@ module "azure_aks" { resource_group = azurerm_resource_group.main.id } + agents_pool_max_surge = "10%" + depends_on = [azurerm_resource_group.main] agents_size = var.vm_size diff --git a/modules/base/outputs.tf b/modules/base/outputs.tf index c3269f1..cb004b2 100644 --- a/modules/base/outputs.tf +++ b/modules/base/outputs.tf @@ -42,6 +42,11 @@ output "aks_server_app_id" { value = data.azuread_service_principal.aks.client_id } +output "aks_oidc_issuer_url" { + description = "Issuer URL for the OpenID Connect discovery endpoint" + value = module.azure_aks.oidc_issuer_url +} + # Ingress outputs output "ingress_nginx_external_ip" { diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..68cdfce --- /dev/null +++ b/outputs.tf @@ -0,0 +1,4 @@ +output "aks_cluster_issuer_url" { + description = "Issuer URL for the OpenID Connect discovery endpoint" + value = module.base.aks_oidc_issuer_url +}