New 3.6 feature: support for Azure managed identities

Support for Azure managed identities

Available to try now in the 3.6 edge snap: sudo snap install juju --channel=3.6/edge

3.6-beta2-8dce573 2024-07-11 (27982) 100MB - (or later)

Coming soon to the upcoming 3.6 beta2 release.

It’s now possible to configure Juju to assign a User Managed Identity to Juju controllers. This allows the controllers to gain the privileges they need to manage resources in the cluster without having to store an actual end user credential on the controller. Traditionally, upon bootstrap, Juju would copy the user’s credential secrets to the controller and use that when making API calls to the cloud. Now, the fact that a User Managed Identity is attached to each controller means that this is no longer necessary; the recommended approach would result in no credential-type secrets being required to bootstrap or operate Juju. The controller gets the authorisation it needs to manage cloud resources from the managed identity.

There’s 3 ways of using the feature:

  1. Recommended: create a managed identity and tell Juju to use it when bootstrapping. This method allows bootstrap without needing any credential type secrets.
  2. Allow Juju to create the managed identity. This method requires a conventional credential (eg service principal) to do the bootstrap, but thereafter the controller operates without any user credential.
  3. Create a managed identity and tell Juju to use it when bootstrapping but use a conventional credential to bootstrap.

Creating a managed identity

To create a managed identity for Juju to use, you will need to use the Azure CLI and be logged in to your account. This is a set up step that can be done ahead of time by an administrator.

The 4 values below need to be filled in according to your requirements.

$ export group=someresourcegroup
$ export location=someregion
$ export role=myrolename
$ export identityname=myidentity
$ export subscription=mysubscription_id

The role definition and role assignment can be scoped to either the subscription or a particular resource group. If scoped to a resource group, this group needs to be provided to Juju when bootstrapping so that the controller resources are also created in that group.

For a subscription scoped managed identity:

$ az group create --name "${group}" --location "${location}"
$ az identity create --resource-group "${group}" --name "${identityname}"
$ mid=$(az identity show --resource-group "${group}" --name "${identityname}" --query principalId --output tsv)
$ az role definition create --role-definition "{
  	\"Name\": \"${role}\",
  	\"Description\": \"Role definition for a Juju controller\",
  	\"Actions\": [
            	\"Microsoft.Compute/*\",
            	\"Microsoft.KeyVault/*\",
            	\"Microsoft.Network/*\",
            	\"Microsoft.Resources/*\",
            	\"Microsoft.Storage/*\",
            	\"Microsoft.ManagedIdentity/userAssignedIdentities/*\"
  	],
  	\"AssignableScopes\": [
        	\"/subscriptions/${subscription}\"
  	]
  }"
$ az role assignment create --assignee-object-id "${mid}" --assignee-principal-type "ServicePrincipal" --role "${role}" --scope "/subscriptions/${subscription}"

A resource scoped managed identity is similar except:

  • the role definition assignable scopes becomes
      \"AssignableScopes\": [
            \"/subscriptions/${subscription}/resourcegroups/${group}\"
      ]
  • the role assignment scope becomes

--scope "/subscriptions/${subscription}/resourcegroups/${group}"

  • the bootstrap command specifies the resource group to use for the controller

juju bootstrap azure --config resource-group-name="${group}" ...

Method 1 - bootstrap with no user secrets

This is the recommended approach. It does require that the managed identity and the Juju resources are created on the same subscription. If different subscriptions are required, you’ll need to use Method 3 with a conventional credential to initially bootstrap.

You’ll also need to do this from either the Azure Cloud Shell or a jump host running in Azure in order to allow the cloud metadata endpoint to be reached.

Create a local Juju managed identity credential. This credential contains no user secrets, just a path to the managed identity to use, and the subscription id.

$ juju add-credential azure --client
Enter credential name: test
Regions
  centralus
  eastus
  eastus2
  ...
  polandcentral

Select region [any region, credential is not region specific]:
Auth Types
  interactive
  service-principal-secret
  managed-identity

Select auth type [interactive]: managed-identity
Enter managed-identity-path: somegroup/myidentity
Enter subscription-id: 2eebf55a-1e02-45d8-a299-02aed8aea00b
Credential "test" added locally for cloud "azure".

The “managed-identity-path” is of the form <resourcegroup>/<identityname>

Now bootstrap.

$ juju bootstrap azure mycontroller

If you want the controller to be in the same resource group as used for the managed identity created above, the specify it at bootstrap.

$ juju bootstrap azure --config resource-group-name=somegroup mycontroller

Be aware though that in the above scenario, the managed identity will be deleted when the controller is destroyed.

Method 2 - conventional bootstrap, Juju creates managed identity

This is done using an instance-role constraint set to “auto”:

juju bootstrap azure --constraints "instance-role=auto"

Here, you’ll need a local service principal credential to have been created,

Method 3 - conventional credential, user creates managed identity.

Again, you’ll need a local service principal credential to have been created,

The managed identity for Juju to use is identified by subscription, resource group and name. Depending on how it’s been created, you’ll need to specify one or more of these attributes. The managed identity is created as explained earlier.

Scenario 1

Managed identity is created in a resource group on the same subscription.

Use the format resourcegroup/identityname:

juju bootstrap azure --constraints "instance-role=resourcegroup/identityname"

Scenario 2

Managed identity is created in a resource group on a different subscription.

Use the format subscription/resourcegroup/identityname:

juju bootstrap azure --constraints "instance-role=subscription/resourcegroup/identityname"

Scenario 3

Managed identity is created in a resource group and that resource group is used to host the controller model.

Use the format identityname:

juju bootstrap azure --config resource-group-name=somegroup --constraints "instance-role=identityname"

3 Likes

Great feature, thank you for on working on this.
Few comments to this blog:

  1. Would be good to mention that VM running Juju cli has to have access granted to manually created MI via identity assign, otherwise bootstrap will fail.

  2. Create a managed identity and tell Juju to use it when bootstrapping but use a conventional credential to bootstrap

    this sentence is hard to comprehend.
    Maybe something like

    Create the managed identity manually. This method requires a conventional credential (eg service principal) to do the bootstrap, but thereafter the controller operates without any user credential.

    would be better, similar to option 2, just changing the first part of it.

  1. $ export group=someresourcegroup

    later on

    $ juju bootstrap azure --config resource-group-name=somegroup mycontroller

    I suggest having same group name across the doc to avoid confusion.