diff --git a/tf-modules/azure/github-actions/README.md b/tf-modules/azure/github-actions/README.md new file mode 100644 index 0000000..0145580 --- /dev/null +++ b/tf-modules/azure/github-actions/README.md @@ -0,0 +1,97 @@ +# Azure GitHub Actions Secrets and Variables + +Configuration in this directory registers an Azure app to be used in CI, +generates a client secret for the app, creates a custom role with the requested +permissions and assigns the role to the Azure app service account. The app +client details are then written to GitHub actions secrets and variables of a +given repository. + +By default, the following GitHub actions secrets are created: +- `ARM_CLIENT_ID` +- `ARM_CLIENT_SECRET` +- `ARM_SUBSCRIPTION_ID` +- `ARM_TENANT_ID` + +and `TF_VAR_azure_location` actions variable is created. All of these names +are overridable, see `variables.tf`. + +It also supports adding custom secrets and variables in addition to the above. + +**NOTE:** Overwriting existing GitHub secrets and variables are not supported. + +## Usage + +```hcl +module "azure_gh_actions" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/github-actions" + + azure_owners = ["owner-id-1", "owner-id-2"] + azure_app_name = "test-app-1" + azure_app_description = "A test app." + azure_permissions = [ + "Microsoft.Kubernetes/*", + "Microsoft.ContainerRegistry/*" + ] + azure_location = "eastus" + + github_project = "repo-name" + + github_variable_custom = { + "SOME_VAR1" = "some-val1", + "SOME_var2" = "some-val2" + } + github_secret_custom = { + "SECRET1" = "some-secret1", + "SECRET2" = "some-secret2" + } +} +``` + +## Azure Requirements + +AzureAD permissions: +- To use a Service Account, grant the following Microsoft Graph API permissions: + - `Application.ReadWrite.OwnedBy` + - `Directory.Read.All` +- To use a User account, ensure that the user has one of the following + directory roles: + - `Application Administrator` + - `Global Administrator` + +Refer [azuread_application docs](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application) +and [azuread_service_principal docs](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal) +for more details. + +AzureRM permissions: +- The following IAM permissions are required when using a Service Account: + - `Microsoft.Resources/subscriptions/read` + - `Microsoft.Authorization/roleDefinitions/write` + - `Microsoft.Authorization/roleDefinitions/delete` + - `Microsoft.Authorization/roleAssignments/read` + - `Microsoft.Authorization/roleAssignments/write` + - `Microsoft.Authorization/roleAssignments/delete` + +## GitHub Requirements + +Create a GitHub fine-grained token for the target repository with the following +repository permissions: +- `Read access to metadata` +- `Read and Write access to actions variables and secrets` + +## Provider Configuration + +Configure the AzureRM, AzureAD and Github providers with the following +environment variables: +```sh +export ARM_CLIENT_ID="" +export ARM_CLIENT_SECRET="" +export ARM_SUBSCRIPTION_ID="" +export ARM_TENANT_ID="" + +export GITHUB_TOKEN="" +``` + +**NOTE:** When using Azure user account, logging in using the Azure CLI should +be enough to configure the Azure providers. + +Check the respective provider docs for more details. diff --git a/tf-modules/azure/github-actions/main.tf b/tf-modules/azure/github-actions/main.tf new file mode 100644 index 0000000..628c30d --- /dev/null +++ b/tf-modules/azure/github-actions/main.tf @@ -0,0 +1,94 @@ +provider "azurerm" { + skip_provider_registration = true + features {} +} + +data "azurerm_subscription" "primary" {} +data "azuread_client_config" "current" {} + +# Register application, create service principal for it and generate client +# secret. +resource "azuread_application" "app" { + display_name = var.azure_app_name + owners = concat(var.azure_owners, [data.azuread_client_config.current.object_id]) +} + +resource "azuread_service_principal" "app_sp" { + application_id = azuread_application.app.application_id + use_existing = true +} + +resource "azuread_application_password" "app_secret" { + application_object_id = resource.azuread_application.app.id + display_name = var.azure_app_secret_name +} + +# Define custom role. +resource "azurerm_role_definition" "role" { + name = var.azure_app_name + scope = data.azurerm_subscription.primary.id + description = var.azure_app_description + + permissions { + actions = var.azure_permissions + } + + assignable_scopes = [ + data.azurerm_subscription.primary.id, + ] +} + +# Assign role to the registered app's service principal. +resource "azurerm_role_assignment" "assignment" { + scope = data.azurerm_subscription.primary.id + role_definition_id = azurerm_role_definition.role.role_definition_resource_id + principal_id = azuread_service_principal.app_sp.object_id +} + +# Add client details in github repo. +resource "github_actions_secret" "client_id" { + repository = var.github_project + secret_name = var.github_secret_client_id_name + plaintext_value = azuread_application.app.application_id +} + +resource "github_actions_secret" "client_secret" { + repository = var.github_project + secret_name = var.github_secret_client_secret_name + plaintext_value = azuread_application_password.app_secret.value +} + +resource "github_actions_secret" "subscription_id" { + repository = var.github_project + secret_name = var.github_secret_subscription_id_name + plaintext_value = data.azurerm_subscription.primary.subscription_id +} + +resource "github_actions_secret" "tenant_id" { + repository = var.github_project + secret_name = var.github_secret_tenant_id_name + plaintext_value = azuread_service_principal.app_sp.application_tenant_id +} + +resource "github_actions_variable" "location" { + repository = var.github_project + variable_name = var.github_variable_location_name + value = var.azure_location +} + +resource "github_actions_variable" "custom" { + for_each = var.github_variable_custom + + repository = var.github_project + variable_name = each.key + value = each.value +} + +resource "github_actions_secret" "custom" { + # Mark only the key as nonsensitive. + for_each = nonsensitive(toset(keys(var.github_secret_custom))) + + repository = var.github_project + secret_name = each.key + plaintext_value = var.github_secret_custom[each.key] +} diff --git a/tf-modules/azure/github-actions/variables.tf b/tf-modules/azure/github-actions/variables.tf new file mode 100644 index 0000000..18be5fa --- /dev/null +++ b/tf-modules/azure/github-actions/variables.tf @@ -0,0 +1,81 @@ +variable "azure_owners" { + description = "Object IDs of the Azure resource owners" + type = list(string) + default = [] +} + +variable "github_project" { + description = "Name of the GitHub project to add secrets to" + type = string +} + +variable "azure_app_name" { + description = "Name of the Azure app to register" + type = string +} + +variable "azure_app_description" { + description = "Description of the Azure app, will be used to provide context to the role assigned to the app" + type = string +} + +variable "azure_app_secret_name" { + description = "Name of the Azure app secret" + type = string + default = "tf-generated-secret" +} + +variable "azure_permissions" { + description = "List of permissions to grant to the created app" + type = list(string) + default = [] +} + +variable "github_secret_client_id_name" { + description = "GitHub secret name for Azure app client ID" + type = string + default = "ARM_CLIENT_ID" +} + +variable "github_secret_client_secret_name" { + description = "GitHub secret name for Azure app client secret" + type = string + default = "ARM_CLIENT_SECRET" +} + +variable "github_secret_subscription_id_name" { + description = "GitHub secret name for Azure subscription ID" + type = string + default = "ARM_SUBSCRIPTION_ID" +} + +variable "github_secret_tenant_id_name" { + description = "GitHub secret name for Azure tenant ID" + type = string + default = "ARM_TENANT_ID" +} + +variable "github_variable_location_name" { + description = "GitHub variable name for Azure location" + type = string + default = "TF_VAR_azure_location" +} + +variable "azure_location" { + description = "Azure location used by the tests" + type = string + default = "eastus" +} + +variable "github_variable_custom" { + description = "A map of custom GitHub variables to be created" + type = map(string) + default = {} +} + +variable "github_secret_custom" { + description = "A map of custom GitHub secrets to be created" + type = map(string) + default = {} + sensitive = true +} diff --git a/tf-modules/azure/github-actions/versions.tf b/tf-modules/azure/github-actions/versions.tf new file mode 100644 index 0000000..38b94e9 --- /dev/null +++ b/tf-modules/azure/github-actions/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.62.1" + } + + azuread = { + source = "hashicorp/azuread" + version = ">= 2.39.0" + } + + github = { + source = "integrations/github" + version = ">= 5.28.1" + } + } +}