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

Add managed identities support to RG setup scripts #837

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has any of this been developed with an eye towards rectifying an existing setup?

Copy link
Contributor Author

@eujing eujing Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bpkroth that is a good point. Unfortunately I have only tested this with setting up from scratch.
However, in theory I think this should work for existing resource groups.

Assuming the existing RG was set up with these scripts, I think all the az cli calls (need to confirm) are idempotent.
For example, using the same ARM template and parameters should give us back the required details of the resources without changing them too much (same naming conventions)

Then the new block of logic for managed identity should then be apply to those existing resources (VMs, storage accounts, RG role)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might need to add end-to-end tests to validate these finally, though maybe in a separate PR.

Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,42 @@ The *control plane RG* is a container for the *persistent* resources of MLOS (re

```shell
# With Powershell, recommended to use Powershell 7
# If provisioning results DB include '-resultsDbArmsParamsFile', otherwise omit

# If setting up with a Service Principal
./setup-rg.ps1 `
-controlPlaneArmParamsFile $controlPlaneArmParamsFile `
-resultsDbArmParamsFile $resultsDbArmParamsFile # If provisioning results DB, otherwise omit `
-servicePrincipalName $servicePrincipalName `
-resourceGroupName $resourceGroupName `
-resultsDbArmParamsFile $resultsDbArmParamsFile `
-servicePrincipalName $servicePrincipalName `
-certName $certName

# If setting up with a Managed Identity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give more context in the README about this part?
When should one use a Managed Identity? How does one set it up, etc.

./setup-rg.ps1 `
-controlPlaneArmParamsFile $controlPlaneArmParamsFile `
-resourceGroupName $resourceGroupName `
-resultsDbArmParamsFile $resultsDbArmParamsFile `
-managedIdentityname $managedIdentityName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-managedIdentityname $managedIdentityName
-managedIdentityName $managedIdentityName

consistent camelCasing please

```

```sh
# With bash
# If provisioning results DB include '--resultsDbArmsParamsFile', otherwise omit

# If setting up with a Service Principal
./setup-rg.sh \
--controlPlaneArmParamsFile $controlPlaneArmParamsFile \
--resourceGroupName $resourceGroupName \
--resultsDbArmParamsFile $resultsDbArmParamsFile \
--servicePrincipalName $servicePrincipalName \
--resourceGroupName $resourceGroupName \
--certName $certName

# If setting up with a Managed Identity
./setup-rg.sh \
--controlPlaneArmParamsFile $controlPlaneArmParamsFile \
--resourceGroupName $resourceGroupName \
--resultsDbArmParamsFile $resultsDbArmParamsFile \
--managedIdentityname $managedIdentityName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
--managedIdentityname $managedIdentityName
--managedIdentityName $managedIdentityName

```

where `$*ArmParamsFile` can be the corresponding `*.parameters.json` and from before. However, it also follows the same usage as `--parameters` in [az deployment group create](https://learn.microsoft.com/en-us/cli/azure/deployment/group?view=azure-cli-latest#az-deployment-group-create-examples).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,13 @@
"kvName": {
"type": "string",
"value": "[variables('kvName')]"
},
"storageAccountNames": {
"type": "array",
"copy": {
"count": "[length(parameters('storageAccountLocations'))]",
"input": "[concat(variables('storageAccountName'), parameters('storageAccountLocations')[copyIndex()])]"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ param(
# Results DB ARM template params
[Parameter(Mandatory=$False)]
[string] $resultsDbArmParamsFile,
# Other params
[Parameter(Mandatory=$True)]
[string] $servicePrincipalName,
[Parameter(Mandatory=$True)]
[string] $resourceGroupName,
[Parameter(Mandatory=$True)]
# Managed Identity params
[Parameter(Mandatory=$True, ParameterSetName="ByMI")]
Copy link
Contributor

@bpkroth bpkroth Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are using ServicePrincipals and ManagedIdentity mutually exclusive?
Why can't we combine those still?
And if not, can the feedback to the user be improved to note that that's not the case?

[string] $managedIdentityName,
# Service Principal params
[Parameter(Mandatory=$True, ParameterSetName="BySP")]
[string] $servicePrincipalName,
[Parameter(Mandatory=$True, ParameterSetName="BySP")]
[string] $certName,
[Parameter(Mandatory=$False)]
[Parameter(Mandatory=$False, ParameterSetName="BySP")]
[int] $certExpirationYears = 1
)

Expand All @@ -36,6 +39,9 @@ if (!$?) {
return
}

$vmName = $deploymentResults.properties.outputs.vmName.value
$storageAccountNames = $deploymentResults.properties.outputs.storageAccountNames.value

# Conditional provisioning of results DB
if ($resultsDbArmParamsFile) {
Write-Output "Provisioning results DB..."
Expand All @@ -51,7 +57,6 @@ if ($resultsDbArmParamsFile) {
}
else {
$dbName = $dbDeploymentResults.properties.outputs.dbName.value
$vmName = $deploymentResults.properties.outputs.vmName.value
$vmIpAddress = $deploymentResults.properties.outputs.vmIpAddress.value

# VM IP access for results DB
Expand Down Expand Up @@ -83,41 +88,77 @@ $certThumbprint = az keyvault certificate show `
2>$null `
|| "NOCERT"

if ($certThumbprint == "NOCERT") {
# The cert does not exist yet.
# Create the service principal if doesn't exist, storing the cert in the keyvault
# If it does exist, this also patches the current service principal with the role
az ad sp create-for-rbac `
--name $servicePrincipalName `
--role "Contributor" `
--scopes $resourceGroupId `
--create-cert `
--cert $certName `
--keyvault $kvName `
--years $certExpirationYears
} else {
# The cert already exists in the keyvault.

# Ensure the SP exists with correct roles, without creating a cert.
az ad sp create-for-rbac `
--name $servicePrincipalName `
--role "Contributor" `
--scopes $resourceGroupId `

# SP's certs, which are stored in the registered application instead
$servicePrincipalAppId = az ad sp list `
--display-name $servicePrincipalName `
--query "[?servicePrincipalType == 'Application'].appId" `
--output tsv
$spCertThumbprints = az ad app credential list `
--id $servicePrincipalAppId `
--cert `
--query "[].customKeyIdentifier" `
--output tsv

if ($spCertThumbprints.Contains($certThumbprint)) {
Write-Output "Keyvault contains the certificate '$certName' that is linked to the service principal '$servicePrincipalName' already."
} else {
Write-Warning "Keyvault already contains a certificate called '$certName', but is not linked with the service principal '$servicePrincipalName'! Skipping cert handling"
switch ($PSCmdlet.ParameterSetName) {
"BySP" {
if ($certThumbprint -eq "NOCERT") {
# The cert does not exist yet.
# Create the service principal if doesn't exist, storing the cert in the keyvault
# If it does exist, this also patches the current service principal with the role
az ad sp create-for-rbac `
--name $servicePrincipalName `
--role "Contributor" `
--scopes $resourceGroupId `
--create-cert `
--cert $certName `
--keyvault $kvName `
--years $certExpirationYears
} else {
# The cert already exists in the keyvault.

# Ensure the SP exists with correct roles, without creating a cert.
az ad sp create-for-rbac `
--name $servicePrincipalName `
--role "Contributor" `
--scopes $resourceGroupId `

# SP's certs, which are stored in the registered application instead
$servicePrincipalAppId = az ad sp list `
--display-name $servicePrincipalName `
--query "[?servicePrincipalType == 'Application'].appId" `
--output tsv
$spCertThumbprints = az ad app credential list `
--id $servicePrincipalAppId `
--cert `
--query "[].customKeyIdentifier" `
--output tsv

if ($spCertThumbprints.Contains($certThumbprint)) {
Write-Output "Keyvault contains the certificate '$certName' that is linked to the service principal '$servicePrincipalName' already."
} else {
Write-Warning "Keyvault already contains a certificate called '$certName', but is not linked with the service principal '$servicePrincipalName'! Skipping cert handling"
}
}
break
}
"ByMI" {
# Ensure the user managed identity is created
$miId = az identity create `
--name $managedIdentityName `
--resource-group $resourceGroupName `
--query "principalId" --output tsv

Write-Output "Using managed identity $managedIdentityName with principalId $miId"

Write-Output "Assigning the identity access to the Resource Group..."
az role assignment create `
--assignee $miId `
--role "Contributor" `
--scope $resourceGroupId

# Assign the identity to the VM
Write-Output "Assigning the identity to the VM..."
az vm identity assign --name $vmName --resource-group $resourceGroupName --identities $managedIdentityName

# Assign the identity access to the storage accounts
foreach ($storageAccountName in $storageAccountNames) {
Write-Output "Assigning the identity the role for $storageAccountName..."
$storageAccountResourceId = az storage account show --name $storageAccountName --resource-group $resourceGroupName --query "id" --output tsv
az role assignment create `
--assignee $miId `
--role "Storage File Data Privileged Contributor" `
--scope $storageAccountResourceId
}
break
}
}

Loading
Loading