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

adding scaffolding for oidc method #23

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
12 changes: 12 additions & 0 deletions oidc/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# change values as needed
name="tfscaffold"
suffix="dev"
location="northeurope"

spName="sp-$name-$suffix"
rg="rg-$name-$suffix"
tag="$suffix"
saName="stac0${name}0${suffix}"
scName="blob0${name}0${suffix}"

saSku="Standard_ZRS"
5 changes: 5 additions & 0 deletions oidc/.env.powershell
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# change values as needed
name=tfscaffold
suffix=dev
location=northeurope
saSku=Standard_ZRS
50 changes: 50 additions & 0 deletions oidc/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Terraform scaffold for Azure

This repo contains everything to get started with Terraform on Azure. It sets you up to use the `azurerm` backend with Service Principal authentication via OIDC.

[Terraform Backend Docs for azurerm](https://developer.hashicorp.com/terraform/language/settings/backends/azurerm#backend-azure-ad-service-principal-or-user-assigned-managed-identity-via-oidc-workload-identity-federation)

## What you will get
After executing the below steps you will get:

- a service principal used to run Terraform on behalf
- a Storage Container used to store the Terraform state file

## Requirements

This project requires the following:

- Bash or PowerShell (you can use [Azure Cloud Shell](http://shell.azure.com/))
- for Bash you need to have [jq](https://stedolan.github.io/jq/) installed
- Azure CLI (authenticated)
- the executing user needs Subscription owner access (to give owner access to the Service Principal for creating managed identities and assigning roles) as well as the Application Developer role in AAD (to create the Service Principal)

## Get started with Bash

Execute the following steps to get started:

1. Authenticate against Azure by executing `az login`
1. Optional: Export your Tenant (`tenantId`) and Subscription ID (`subscriptionId`) if you don't like to deploy with your `az` defaults.
1. Customize `.env` based on your needs and naming conventions (Make sure you met all [Azure naming rules and restrictions](https://docs.microsoft.com/azure/azure-resource-manager/management/resource-name-rules)).
1. Update the \<tokens> in `federated_credential.json`.
1. Execute `up.sh` to deploy everything needed
1. Grant admin consent for the created app registrations (Terraform will then be allowed to create app registrations and groups in Azure AD). This needs Azure Active Directory global admin access. Find more details on how to grant consent [here](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent).

#TODO
## Get started with PowerShell

Execute the following steps to get started:

1. Authenticate against Azure by executing `az login`
1. Optional: Create environment variables for Tenant (`tenantId`) and Subscription ID (`subscriptionId`) or call the script with the parameters `-tenantId` and `-subscriptionId` if you don't like to deploy with your `az` defaults.
1. Customize `.env.powershell` based on your needs and naming conventions (Make sure you met all [Azure naming rules and restrictions](https://docs.microsoft.com/azure/azure-resource-manager/management/resource-name-rules)).
1. Update the \<tokens> in `federated_credential.json`.
1. Execute `up.ps1` to deploy everything needed
1. Grant admin consent for the created app registrations (Terraform will then be allowed to create app registrations and groups in Azure AD). This needs Azure Active Directory global admin access. Find more details on how to grant consent [here](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent).


## Disclaimer

The `up.sh` script asks you whether you would like to map our Partner ID to the created Service Principal. Feel free to opt-out or remove the marked lines if you don't like to support us.

> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 changes: 9 additions & 0 deletions oidc/federated_credential.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "<stage>-service-connection",
"issuer": "https://vstoken.dev.azure.com/<organizationId>",
"subject": "sc://<organizationName>/<projectName>/<serviceConnectionName>",
"description": "Terraform pipeline",
"audiences": [
"api://AzureADTokenExchange"
]
}
61 changes: 61 additions & 0 deletions oidc/resources.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
param sa_name string
param sa_sku string
param sc_name string
param tag string
param location string

resource tf_sa 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: sa_name
location: location
tags: {
environment: tag
managedBy: 'tfScaffolding'
}
sku: {
name: sa_sku
}
kind: 'StorageV2'
properties: {
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
encryption: {
services: {
file: {
enabled: true
}
blob: {
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
}
}

resource tf_sb 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: tf_sa
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: 30
allowPermanentDelete: false
}
containerDeleteRetentionPolicy: {
enabled: true
days: 30
allowPermanentDelete: false
}
}
}

resource tf_sc 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
parent: tf_sb
name: sc_name
}
152 changes: 152 additions & 0 deletions oidc/up.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
[CmdletBinding()]
param (
[Parameter()]
[string]
$subscriptionId = $env:subscriptionId,

[Parameter()]
[string]
$tenantId = $env:tenantId
)

# Error trapping
trap {
Write-Host "Error on line $($($_.InvocationInfo.ScriptLineNumber)): $($_.Exception.Message)"
exit 1
}

# If $subscriptionId is not set, try to set it using the az CLI
# If $subscriptionId is still not set after that, throw an error
if (-not $subscriptionId) {
$subscriptionId = az account show --query id -o tsv
if (-not $subscriptionId) {
throw "Failed to obtain subscription ID"
}
}
Write-Host "Subscription ID set to $subscriptionId"

# If $tenantId is not set, try to set it using the az CLI
# If $tenantId is still not set after that, throw an error
if (-not $tenantId) {
$tenantId = az account show --query homeTenantId -o tsv
if (-not $tenantId) {
throw "Failed to obtain tenant ID"
}
}
Write-Host "Tenant ID set to $tenantId"

# Load independent variables from .env.powershell file
$envVars = Get-Content .env.powershell | Out-String | ConvertFrom-StringData

# Declare dependent variables
$spName = "sp-$($envVars['name'])-$($envVars['suffix'])"
$rg = "rg-$($envVars['name'])-$($envVars['suffix'])"
$tag = $envVars['suffix']
$saName = "stac0$($envVars['name'])0$($envVars['suffix'])"
$scName = "blob0$($envVars['name'])0$($envVars['suffix'])"

# Set subscription
az account set --subscription "$subscriptionId"

# Creates resource group
az group create `
--name $rg `
--location "$($envVars['location'])" `
--tags environment="$tag" `
--subscription "$subscriptionId"
if (-not $?) {
throw "Failed to create resource group"
}
Write-Host "Resource group created..."

# Creates a service principal if it doesn't exist
# Needs to be owner to create managed identities and assign roles
$sp = az ad sp list --display-name $spName --query "[].displayName" -o tsv
if ($sp -eq $spName) {
Write-Host "Service principal already exists..."
$spId = az ad sp list --display-name $spName --query "[].appId" -o tsv
}
else {
$sp = az ad sp create-for-rbac `
--name $spName `
--role "Owner" `
--scopes "/subscriptions/$subscriptionId" `
--years 99 | ConvertFrom-Json
Write-Host "Service principal created..."
# Set service principal id variable
$spId = $sp.appId
$parametersPath = "./federated_credential.json"
az ad app federated-credential create --id $spId --parameters $parametersPath
Write-Host "Federated credential created..."
}

# Add ADD API permissions - Group.ReadWrite.All, GroupMember.ReadWrite.All, User.Read.All
az ad app permission add `
--id "$spId" `
--api 00000003-0000-0000-c000-000000000000 `
--api-permissions `
62a82d76-70ea-41e2-9197-370581804d09=Role `
dbaae8cf-10b5-4b86-a4a1-f871c94c6695=Role `
df021288-bdef-4463-88db-98f22de89214=Role
if (-not $?) {
throw "Failed to add ADD API permissions"
}
Write-Host "ADD API permissions added..."

# Update roles
az role assignment create `
--assignee "$spId" `
--scope "/subscriptions/$subscriptionId" `
--role "Monitoring Metrics Publisher"
if (-not $?) {
throw "Failed to update roles"
}
Write-Host "Roles updated..."

# Get local user
$userId = az ad signed-in-user show --query id -o tsv
if (-not $?) {
throw "Failed to get local user"
}
Write-Host "Local user fetched..."

# Creates resources
az deployment group create `
--name "$($envVars['name'])" `
--resource-group "$rg" `
--template-file ./resources.bicep `
--subscription "$subscriptionId" `
--mode Incremental `
--parameters `
sa_name="$saName" `
sa_sku="$($envVars['saSku'])" `
sc_name="$scName" `
tag="$tag" `
location="$($envVars['location'])"
if (-not $?) {
throw "Failed to create deployment"
}
Write-Host "Deployment created..."

# Update roles
az role assignment create `
--assignee "$spId" `
--scope "/subscriptions/$subscriptionId/resourceGroups/$rg/providers/Microsoft.Storage/storageAccounts/$saName" `
--role "Storage Blob Data Owner"
if (-not $?) {
throw "Failed to update roles"
}
Write-Host "Roles updated..."

# Map Partner ID (optional)
Write-Host "---"
$response = Read-Host "Do you like to map our Partner ID? [y/N]"
if ($response -imatch "^(y|yes)$") {
az extension add --name managementpartner
az login --tenant "$tenantId" --service-principal -u "$spId" -p "$spSecret"
az managementpartner create --partner-id 3699617
az logout
Write-Host "---"
Write-Host "Please login."
az login
}
Loading