Skip to content

Commit

Permalink
Update Terraform doc (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
geekzter authored May 2, 2024
1 parent ffcbd12 commit 877ef7d
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 77 deletions.
17 changes: 10 additions & 7 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
name: Generate terraform docs
on:
- pull_request
- push

jobs:
docs:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v4

- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v2
Expand All @@ -21,21 +20,25 @@ jobs:
shell: bash
working-directory: terraform/azure-devops/create-service-connection

- name: Render terraform docs and push changes back to PR
- name: Render terraform docs
uses: terraform-docs/gh-actions@main
with:
config-file: doc-gen/.terraform-docs.yml
working-dir: terraform/azure-devops/create-service-connection
output-format: markdown table
git-push: false

- name: Fix .git permissions
run: sudo chmod -R ugo+rwX .git

- name: Commit changes
run: |
git config user.name github-actions
git config user.email [email protected]
git add graph.png
git add README.md
git config --local user.email ""
git config --local user.name "GitHub Actions"
git diff-index --quiet HEAD || git commit -m "Update docs"
git push
# git diff-index --quiet HEAD || (git add -A && git commit -m'[bot] update files' --allow-empty && git push -f)
shell: bash
working-directory: terraform/azure-devops/create-service-connection
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ This repo contains a few [PowerShell](https://github.com/PowerShell/PowerShell)

## Azure DevOps

- Manage Azure Service Connection with [Terraform](terraform/azure-devops/create-service-connection/README.md) to create Managed Identity, Federated Identity Credential, secret rotation and ITSM metadata
- Configure Terraform [azuread](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs#authenticating-to-azure-active-directory)/[azurerm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) provider `ARM_*` environment variables to use the [AzureCLI](https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/azure-cli-v2?view=azure-pipelines) task [Service Connection](https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure?view=azure-devops):
[set_terraform_azurerm_vars.ps1](scripts/azure-devops/set_terraform_azurerm_vars.ps1)
- Create Managed Identity for Service Connection with Workload identity federation: [create_azurerm_msi_oidc_service_connection.ps1](scripts/azure-devops/create_azurerm_msi_oidc_service_connection.ps1)
- Create Managed Identity for Service Connection with Workload identity federation with [Terraform](terraform/azure-devops/create-service-connection/README.md)
- List identities for Azure DevOps Service Connections in Entra ID pertaining to Azure DevOps organization and (optionally) project: [list_service_connection_identities.ps1](scripts/azure-devops/list_service_connection_identities.ps1)
- List Azure DevOps Service Connections in an Azure DevOps organization and project: [list_service_connections.ps1](scripts/azure-devops/list_service_connections.ps1)
- 'Pretty-name' Entra ID applications created for Service Connections, so the Service Connection name is included in the application display name: [rename_service_connection_applications.ps1](scripts/azure-devops/rename_service_connection_applications.ps1)
Expand Down
110 changes: 76 additions & 34 deletions terraform/azure-devops/create-service-connection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,74 @@

[![Build Status](https://dev.azure.com/geekzter/Pipeline%20Playground/_apis/build/status%2Fcreate-service-connection?branchName=main&label=terraform-ci)](https://dev.azure.com/geekzter/Pipeline%20Playground/_build/latest?definitionId=5&branchName=main)

Many large customers have additional requirements around the management of the Entra ID object that a service connection creates and the permissions it is assigned to.
Azure DevOps uses service connections to connect to services that are targets for cloud infrastructure provisioning and application deployment. The most commonly used service connection is the [Azure Resource Manager service connection](https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure?view=azure-devops). This creates an object in Azure DevOps, an identity in Entra ID and a role assignment in Azure.

These are a few common requirements and constraints:
Many Enterprise customers have requirements around the management of Entra [workload identities](https://learn.microsoft.com/entra/workload-id/workload-identities-overview) (applications, service principals, managed identities) as well as the permissions they are assigned to.

Here are a few common requirements and constraints:

- Creation of app registrations is [disabled in the Entra ID tenant](https://learn.microsoft.com/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications) and/or
the use of Managed Identities for Azure access is mandated
- Specific secret expiration and auto-rotation control
- Custom role assignments for Azure [data plane](https://learn.microsoft.com/azure/azure-resource-manager/management/control-plane-and-data-plane#data-plane) access e.g. [Key Vault](https://learn.microsoft.com/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations), [Kusto](https://learn.microsoft.com/azure/data-explorer/kusto/access-control/role-based-access-control), [Storage](https://learn.microsoft.com/azure/storage/blobs/assign-azure-role-data-access?tabs=portal)
- Creation of app registrations is [disabled in Entra ID](https://learn.microsoft.com/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications) or the use of Managed Identities for Azure access is explicitly mandated
- Required ITSM metadata on Entra ID app registration (IT Service Management Reference, naming convention, notes)
- Co-owners are required to exist for Entra ID app registrations
- The organization has an IT fulfillment process where identities are automatically created based on a service request
- ITSM metadata is required on Entra ID objects (service nanagement reference, naming convention, notes)
- Co-owners are required to exist for Entra ID apps
- Custom role assignments are needed for Azure [data plane](https://learn.microsoft.com/azure/azure-resource-manager/management/control-plane-and-data-plane#data-plane) access e.g. [Key Vault](https://learn.microsoft.com/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations), [Kusto](https://learn.microsoft.com/azure/data-explorer/kusto/access-control/role-based-access-control), [Storage](https://learn.microsoft.com/azure/storage/blobs/assign-azure-role-data-access?tabs=portal)
- Access needs to be granted to multiple Azure subscriptions that are not part of the same management group
- An IT fulfillment process exists where identities are automatically provisioned based on a service request

## Why Terraform?

Terraform employs a provider model which enable all changes to be made by a single tool and configuration:
Terraform employs a provider model which enables all changes to be made by a single tool and configuration:

| Service | Provider | API |
|--------------|----------|-----|
| Azure | [azurerm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) | [Azure Resource Manager REST API](https://learn.microsoft.com/rest/api/resources/) |
| Azure DevOps | [azuredevops](https://registry.terraform.io/providers/microsoft/azuredevops/latest/docs) | [Azure DevOps REST API](https://learn.microsoft.com/rest/api/azure/devops/serviceendpoint/endpoints) |
| Entra ID | [azuread](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs) | [Microsoft Graph API](https://learn.microsoft.com/graph/use-the-api) |

Terraform is a declarative tool that is capable if inferring dependencies to create resources in the correct order. This is the output from `terraform graph`:
[HCL](https://developer.hashicorp.com/terraform/language#about-the-terraform-language), the language used, is declarative and the tool is capable if inferring dependencies to create resources in order. This is the output from `terraform graph`:
![Terraform graph](graph.png)

More information:

- [Overview of Terraform on Azure - What is Terraform?](https://learn.microsoft.com/azure/developer/terraform/overview)
- [Cloud Adoption Framework Infrastructure-as-Code CI/CD security guidance](https://learn.microsoft.com/azure/cloud-adoption-framework/secure/best-practices/secure-devops)
- [Cloud Adoption Framework - Infrastructure-as-Code CI/CD security guidance](https://learn.microsoft.com/azure/cloud-adoption-framework/secure/best-practices/secure-devops)

## Provisioning

Provisioning is a matter of specifying [variables](https://developer.hashicorp.com/terraform/language/values/variables) (see [inputs](#input_azdo_organization_url) below) and running `terraform apply`. To understand how the Terraform configuration can be created in automation, review
Provisioning is a matter of specifying Terraform [variables](https://developer.hashicorp.com/terraform/language/values/variables) (see [inputs](#inputs) below) and running `terraform apply`. To understand how the Terraform configuration can be created in automation, review
[tf_create_azurerm_service_connection.ps1](../../../scripts/azure-devops/tf_create_azurerm_service_connection.ps1) and the
[CI pipeline](azure-pipelines.yml).

### Examples

Terraform variable can be provided as a .auto.tfvars file, see [sample](config.auto.tfvars.sample).

#### App registration with Federated Credential and ITSM data
#### Default configuration

This creates an App registration with Federated Identity Credential and `Contributor` role on the Azure subscription used by the Terraform `azurerm` provider.

```hcl
azdo_organization_url = "https://dev.azure.com/my-organization"
azdo_project_name = "my-project"
```

Pre-requisites:

- The user can create app registrations i.e.:
- Creation of app registrations is not [disabled in Entra ID](https://learn.microsoft.com/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications);
or
- The user is member of a privileged Entra ID role e.g. [Application Developer](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer)
- The user is an owner of the Azure subscription (so role assignment can be performed)

#### Managed Identity with FIC and custom RBAC

This creates a Managed Identity with Federated Identity Credential and custom Azure RBAC (role-based access control) role assignments:

```hcl
azdo_creates_identity = false
azdo_organization_url = "https://dev.azure.com/my-organization"
azdo_project_name = "my-project"
azure_role_assignments = [
{
scope = "/subscriptions/00000000-0000-0000-0000-000000000000"
Expand All @@ -54,8 +79,28 @@ azure_role_assignments = [
{
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg"
role = "Storage Blob Data Contributor"
},
{
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg"
role = "Key Vault Secrets User"
}
]
create_federation = true
create_managed_identity = true
managed_identity_resource_group_id = "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/msi-rg"
```

Pre-requisites:

- A resource group to hold the Managed Identity has been pre-created
- The user is an owner of the Azure scopes to create role assignments on

#### App registration with FIC and ITSM metadata

This creates an Entra ID app registration with IT service reference and notes fields populated as well as specifying co-owners:

```hcl
azdo_creates_identity = false
azdo_organization_url = "https://dev.azure.com/my-organization"
azdo_project_name = "my-project"
create_federation = true
Expand All @@ -65,7 +110,17 @@ entra_app_owner_object_ids = ["00000000-0000-0000-0000-000000000000","111111
entra_service_management_reference = "11111111-1111-1111-1111-111111111111"
```

#### App registration with short-lived secret
Pre-requisites:

- The user can create app registrations i.e.:
- Creation of app registrations is not [disabled in Entra ID](https://learn.microsoft.com/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications);
or
- The user is member of a privileged Entra ID role e.g. [Application Developer](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer)
- The user is an owner of the Azure subscription (so role assignment can be performed)

#### App registration with short-lived secret and constrained RBAC

This creates an Entra ID app registration with secret that expires after 1 hour:

```hcl
azdo_creates_identity = false
Expand All @@ -81,30 +136,17 @@ create_federation = false
create_managed_identity = false
entra_secret_expiration_days = 0 # secret lasts 1 hour
```
Pre-requisites:

#### Managed Identity with Federated Credential

```hcl
azdo_creates_identity = false
azdo_organization_url = "https://dev.azure.com/my-organization"
azdo_project_name = "my-project"
azure_role_assignments = [
{
scope = "/subscriptions/00000000-0000-0000-0000-000000000000"
role = "Contributor"
},
{
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg"
role = "Key Vault Secrets User"
}
]
create_federation = true
create_managed_identity = true
managed_identity_resource_group_id = "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/msi-rg"
```
- The user can create app registrations i.e.:
- Creation of app registrations is not [disabled in Entra ID](https://learn.microsoft.com/entra/identity/role-based-access-control/delegate-app-roles#restrict-who-can-create-applications);
or
- The user is member of a privileged Entra ID role e.g. [Application Developer](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer)
- The user is an owner of the Azure resource group (so role assignment can be performed)

## Terraform Configuration

The (required) variables and output are listed below. Sensitive outputs are masked by default.
Generated with [terraform-docs](https://terraform-docs.io/).

### Providers
Expand Down Expand Up @@ -134,7 +176,7 @@ Generated with [terraform-docs](https://terraform-docs.io/).
| <a name="input_azdo_creates_identity"></a> [azdo_creates_identity](#input_azdo_creates_identity) | Let Azure DevOps create identity for service connection | `bool` | `false` | no |
| <a name="input_azure_role_assignments"></a> [azure_role_assignments](#input_azure_role_assignments) | Role assignments to create for the service connection's identity. If this is empty, the Contributor role will be assigned on the azurerm provider subscription. | `set(object({scope=string, role=string}))` | `[]` | no |
| <a name="input_create_federation"></a> [create_federation](#input_create_federation) | Use workload identity federation instead of a App Registration secret | `bool` | `true` | no |
| <a name="input_create_managed_identity"></a> [create_managed_identity](#input_create_managed_identity) | Creates a Managed Identity instead of a App Registration | `bool` | `true` | no |
| <a name="input_create_managed_identity"></a> [create_managed_identity](#input_create_managed_identity) | Creates a Managed Identity instead of a App Registration | `bool` | `false` | no |
| <a name="input_entra_app_notes"></a> [entra_app_notes](#input_entra_app_notes) | Description to put in the Entra ID app registration notes field | `string` | `null` | no |
| <a name="input_entra_app_owner_object_ids"></a> [entra_app_owner_object_ids](#input_entra_app_owner_object_ids) | Object ids of the users that will be co-owners of the Entra ID app registration | `list(string)` | `null` | no |
| <a name="input_entra_secret_expiration_days"></a> [entra_secret_expiration_days](#input_entra_secret_expiration_days) | Secret expiration in days | `number` | `90` | no |
Expand Down
Loading

0 comments on commit 877ef7d

Please sign in to comment.