Skip to content

Commit

Permalink
feat: Added UDT for Managed identity and Deployment Scripts (Azure#1224)
Browse files Browse the repository at this point in the history
## Description

This pull request introduces two user-defined types for the following
modules:
* Deployment scripts -> (secure) environment variables (tagging module
owner: @sebassem)
* User Assigned Managed Identity -> federated identity credentials
(tagging module owner: @elanzel)
* Removed an unused line of code in
`.github/actions/templates/avm-validateModuleDeployment/action.yml`

Extra context regarding `@secure()` decorator on user-defined type. When
the Bicep template is built the type is referred as a secureString so
the parameter inherits the secureString type and the parameter is secure
during deployment:

<img width="815" alt="Untitled"
src="https://github.com/Azure/bicep-registry-modules/assets/3514513/d2b8972f-6b34-46ed-b3d4-5c68437b263d">

Also double checked if the outputs were working and tested it via this
Bicep template:

```bicep
/* Bicepparam
using './testsecureoutput.bicep'

param parTestingParameter = {
  secureList: [
    {
      name: 'test'
      secureValue: 'test123'
    }
  ]
}
*/

param parTestingParameter environmentVariableType

output outTestBool bool = parTestingParameter != null
output outTestArray array = parTestingParameter != null ? parTestingParameter!.secureList : []
output outTestArrayV2 array = parTestingParameter!.secureList ?? []
output outTestString string = first(parTestingParameter.secureList)!.secureValue!

@secure()
type environmentVariableType = {
  secureList: {
    name: string
    secureValue: string?
    value: string?
  }[]
}?
```

## Pipeline Reference


[![avm.res.managed-identity.user-assigned-identity](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml/badge.svg?branch=johnlokerse%2Fadd-udt-ds-uami)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml)


[![avm.res.resources.deployment-script](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml/badge.svg?branch=johnlokerse%2Fadd-udt-ds-uami)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml)

## Type of Change

<!-- Use the check-boxes [x] on the options that are relevant. -->

- [ ] Update to CI Environment or utlities (Non-module effecting
changes)
- [ ] Azure Verified Module updates:
- [ ] Bugfix containing backwards compatible bug fixes, and I have NOT
bumped the MAJOR or MINOR version in `version.json`:
- [ ] Someone has opened a bug report issue, and I have included "Closes
#{bug_report_issue_number}" in the PR description.
- [ ] The bug was found by the module author, and no one has opened an
issue to report it yet.
- [x] Feature update backwards compatible feature updates, and I have
bumped the MINOR version in `version.json`.
- [ ] Breaking changes and I have bumped the MAJOR version in
`version.json`.
  - [x] Update to documentation

## Checklist

- [x] I'm sure there are no other open Pull Requests for the same
update/change
- [x] I have run `Set-AVMModule` locally to generate the supporting
module files.
- [x] My corresponding pipelines / checks run clean and green without
any errors or warnings
  • Loading branch information
johnlokerse authored Mar 11, 2024
1 parent 266db87 commit 119dc6d
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ runs:
# Load used functions
. (Join-Path $env:GITHUB_WORKSPACE 'avm' 'utilities' 'pipelines' 'e2eValidation' 'regionSelector' 'Get-AzAvailableResourceLocation.ps1')
# Set fucntion input parameters
# Set function input parameters
$functionInput = @{
ModuleRoot = '${{ inputs.modulePath }}'
GlobalResourceGroupLocation = '${{ inputs.deploymentMetadataLocation }}'
Expand All @@ -86,7 +86,6 @@ runs:
$resourceLocation = Get-AzAvailableResourceLocation @functionInput -Verbose
$deploymentLocation = @{}
Write-Verbose ('{0}-{1}' -f 'resourceLocation', $resourceLocation) -Verbose
Write-Output ('{0}={1}' -f 'resourceLocation', $resourceLocation) >> $env:GITHUB_OUTPUT
Expand Down
38 changes: 37 additions & 1 deletion avm/res/managed-identity/user-assigned-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,43 @@ The federated identity credentials list to indicate which token from the externa

- Required: No
- Type: array
- Default: `[]`

**Required parameters**

| Parameter | Type | Description |
| :-- | :-- | :-- |
| [`audiences`](#parameter-federatedidentitycredentialsaudiences) | array | The list of audiences that can appear in the issued token. |
| [`issuer`](#parameter-federatedidentitycredentialsissuer) | string | The URL of the issuer to be trusted. |
| [`name`](#parameter-federatedidentitycredentialsname) | string | The name of the federated identity credential. |
| [`subject`](#parameter-federatedidentitycredentialssubject) | string | The identifier of the external identity. |

### Parameter: `federatedIdentityCredentials.audiences`

The list of audiences that can appear in the issued token.

- Required: Yes
- Type: array

### Parameter: `federatedIdentityCredentials.issuer`

The URL of the issuer to be trusted.

- Required: Yes
- Type: string

### Parameter: `federatedIdentityCredentials.name`

The name of the federated identity credential.

- Required: Yes
- Type: string

### Parameter: `federatedIdentityCredentials.subject`

The identifier of the external identity.

- Required: Yes
- Type: string

### Parameter: `location`

Expand Down
18 changes: 16 additions & 2 deletions avm/res/managed-identity/user-assigned-identity/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ param name string
param location string = resourceGroup().location

@description('Optional. The federated identity credentials list to indicate which token from the external IdP should be trusted by your application. Federated identity credentials are supported on applications only. A maximum of 20 federated identity credentials can be added per application object.')
param federatedIdentityCredentials array = []
param federatedIdentityCredentials federatedIdentityCredentialsType

@description('Optional. The lock settings of the service.')
param lock lockType
Expand Down Expand Up @@ -66,7 +66,7 @@ resource userAssignedIdentity_lock 'Microsoft.Authorization/locks@2020-05-01' =
scope: userAssignedIdentity
}

module userAssignedIdentity_federatedIdentityCredentials 'federated-identity-credential/main.bicep' = [for (federatedIdentityCredential, index) in federatedIdentityCredentials: {
module userAssignedIdentity_federatedIdentityCredentials 'federated-identity-credential/main.bicep' = [for (federatedIdentityCredential, index) in (federatedIdentityCredentials ?? []): {
name: '${uniqueString(deployment().name, location)}-UserMSI-FederatedIdentityCredential-${index}'
params: {
name: federatedIdentityCredential.name
Expand Down Expand Up @@ -143,3 +143,17 @@ type roleAssignmentType = {
@description('Optional. The Resource Id of the delegated managed identity resource.')
delegatedManagedIdentityResourceId: string?
}[]?

type federatedIdentityCredentialsType = {
@description('Required. The name of the federated identity credential.')
name: string

@description('Required. The list of audiences that can appear in the issued token.')
audiences: string[]

@description('Required. The URL of the issuer to be trusted.')
issuer: string

@description('Required. The identifier of the external identity.')
subject: string
}[]?
51 changes: 43 additions & 8 deletions avm/res/managed-identity/user-assigned-identity/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"_generator": {
"name": "bicep",
"version": "0.25.53.49325",
"templateHash": "14161959215427804200"
"templateHash": "11532993654681133775"
},
"name": "User Assigned Identities",
"description": "This module deploys a User Assigned Identity.",
Expand Down Expand Up @@ -103,6 +103,42 @@
}
},
"nullable": true
},
"federatedIdentityCredentialsType": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"metadata": {
"description": "Required. The name of the federated identity credential."
}
},
"audiences": {
"type": "array",
"items": {
"type": "string"
},
"metadata": {
"description": "Required. The list of audiences that can appear in the issued token."
}
},
"issuer": {
"type": "string",
"metadata": {
"description": "Required. The URL of the issuer to be trusted."
}
},
"subject": {
"type": "string",
"metadata": {
"description": "Required. The identifier of the external identity."
}
}
}
},
"nullable": true
}
},
"parameters": {
Expand All @@ -120,8 +156,7 @@
}
},
"federatedIdentityCredentials": {
"type": "array",
"defaultValue": [],
"$ref": "#/definitions/federatedIdentityCredentialsType",
"metadata": {
"description": "Optional. The federated identity credentials list to indicate which token from the external IdP should be trusted by your application. Federated identity credentials are supported on applications only. A maximum of 20 federated identity credentials can be added per application object."
}
Expand Down Expand Up @@ -231,7 +266,7 @@
"userAssignedIdentity_federatedIdentityCredentials": {
"copy": {
"name": "userAssignedIdentity_federatedIdentityCredentials",
"count": "[length(parameters('federatedIdentityCredentials'))]"
"count": "[length(coalesce(parameters('federatedIdentityCredentials'), createArray()))]"
},
"type": "Microsoft.Resources/deployments",
"apiVersion": "2022-09-01",
Expand All @@ -243,19 +278,19 @@
"mode": "Incremental",
"parameters": {
"name": {
"value": "[parameters('federatedIdentityCredentials')[copyIndex()].name]"
"value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].name]"
},
"userAssignedIdentityName": {
"value": "[parameters('name')]"
},
"audiences": {
"value": "[parameters('federatedIdentityCredentials')[copyIndex()].audiences]"
"value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].audiences]"
},
"issuer": {
"value": "[parameters('federatedIdentityCredentials')[copyIndex()].issuer]"
"value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].issuer]"
},
"subject": {
"value": "[parameters('federatedIdentityCredentials')[copyIndex()].subject]"
"value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].subject]"
}
},
"template": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ param serviceShort string = 'miuaimax'
@description('Optional. A token to inject into the name of each resource.')
param namePrefix string = '#_namePrefix_#'

// Set to fixed location as the RP function returns unsupported locations
// Right now (2024/03) the following locations are NOT supported: East Asia, Qatar Central, Malaysia South, Italy North, Israel Central
param enforcedLocation string = 'westeurope'

// ============ //
// Dependencies //
// ============ //
Expand All @@ -36,7 +40,7 @@ module nestedDependencies 'dependencies.bicep' = {
name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies'
params: {
managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}'
location: resourceLocation
location: enforcedLocation
}
}

Expand All @@ -50,7 +54,7 @@ module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem'
name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
params: {
name: '${namePrefix}${serviceShort}001'
location: resourceLocation
location: enforcedLocation
lock: {
kind: 'CanNotDelete'
name: 'myCustomLockName'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ param serviceShort string = 'miuaiwaf'
@description('Optional. A token to inject into the name of each resource.')
param namePrefix string = '#_namePrefix_#'

// Set to fixed location as the RP function returns unsupported locations
// Right now (2024/03) the following locations are NOT supported for federated identity credentials: East Asia, Qatar Central, Malaysia South, Italy North, Israel Central
param enforcedLocation string = 'westeurope'

// ============ //
// Dependencies //
// ============ //
Expand All @@ -41,7 +45,7 @@ module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem'
name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
params: {
name: '${namePrefix}${serviceShort}001'
location: resourceLocation
location: enforcedLocation
lock: {
kind: 'CanNotDelete'
name: 'myCustomLockName'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://aka.ms/bicep-registry-module-version-file-schema#",
"version": "0.1",
"version": "0.2",
"pathFilters": [
"./main.json"
]
Expand Down
1 change: 0 additions & 1 deletion avm/res/resources/deployment-script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,6 @@ The environment variables to pass over to the script. The list is passed as an o

- Required: No
- Type: secureObject
- Default: `{}`

### Parameter: `location`

Expand Down
26 changes: 12 additions & 14 deletions avm/res/resources/deployment-script/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,8 @@ param scriptContent string?
@description('Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead.')
param primaryScriptUri string?

@metadata({
example: '''
secureList: [
{
name: 'string'
secureValue: 'string'
value: 'string'
}
]
'''
})
@description('Optional. The environment variables to pass over to the script. The list is passed as an object with a key name "secureList" and the value is the list of environment variables (array). The list must have a \'name\' and a \'value\' or a \'secretValue\' property for each object.')
@secure()
param environmentVariables object = {}
param environmentVariables environmentVariableType

@description('Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent).')
param supportingScriptUris array?
Expand Down Expand Up @@ -195,7 +183,7 @@ resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
containerSettings: !empty(containerSettings) ? containerSettings : null
storageAccountSettings: !empty(storageAccountResourceId) ? storageAccountSettings : null
arguments: arguments
environmentVariables: !empty(environmentVariables) ? environmentVariables.secureList : []
environmentVariables: environmentVariables != null ? environmentVariables!.secureList : []
scriptContent: !empty(scriptContent) ? scriptContent : null
primaryScriptUri: !empty(primaryScriptUri) ? primaryScriptUri : null
supportingScriptUris: !empty(supportingScriptUris) ? supportingScriptUris : null
Expand Down Expand Up @@ -264,3 +252,13 @@ type roleAssignmentType = {
@description('Optional. The Resource Id of the delegated managed identity resource.')
delegatedManagedIdentityResourceId: string?
}[]?

@secure()
type environmentVariableType = {
@description('Optional. The list of environment variables to pass over to the deployment script.')
secureList: {
name: string
secureValue: string?
value: string?
}[]
}?
36 changes: 31 additions & 5 deletions avm/res/resources/deployment-script/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"_generator": {
"name": "bicep",
"version": "0.25.53.49325",
"templateHash": "6812401706170828518"
"templateHash": "11832942286479289748"
},
"name": "Deployment Scripts",
"description": "This module deploys Deployment Scripts.",
Expand Down Expand Up @@ -118,6 +118,34 @@
}
},
"nullable": true
},
"environmentVariableType": {
"type": "secureObject",
"properties": {
"secureList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"secureValue": {
"type": "string",
"nullable": true
},
"value": {
"type": "string",
"nullable": true
}
}
},
"metadata": {
"description": "Optional. The list of environment variables to pass over to the deployment script."
}
}
},
"nullable": true
}
},
"parameters": {
Expand Down Expand Up @@ -187,10 +215,8 @@
}
},
"environmentVariables": {
"type": "secureObject",
"defaultValue": {},
"$ref": "#/definitions/environmentVariableType",
"metadata": {
"example": "secureList: [\n {\n name: 'string'\n secureValue: 'string'\n value: 'string'\n }\n]\n",
"description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object."
}
},
Expand Down Expand Up @@ -396,7 +422,7 @@
"containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]",
"storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]",
"arguments": "[parameters('arguments')]",
"environmentVariables": "[if(not(empty(parameters('environmentVariables'))), parameters('environmentVariables').secureList, createArray())]",
"environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]",
"scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]",
"primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]",
"supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]",
Expand Down
4 changes: 2 additions & 2 deletions avm/res/resources/deployment-script/version.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://aka.ms/bicep-registry-module-version-file-schema#",
"version": "0.1",
"version": "0.2",
"pathFilters": [
"./main.json"
]
}
}

0 comments on commit 119dc6d

Please sign in to comment.