|
| 1 | +# Managed Identity Using Azure Entra ID Workload Identity (AKS) |
| 2 | + |
| 3 | +This documentation is for instructions on using ambient credentials within Azure Kubernetes Services (AKS). Full documentation on Command Cert Manager Issuer can be found [here](../../README.md). |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +- [kubectl](https://kubernetes.io/docs/reference/kubectl/) installed on your machine and [connected to your AKS cluster](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli#connect-to-the-cluster) |
| 8 | +- [Helm](https://github.com/helm/helm?tab=readme-ov-file#install) 3.x installed |
| 9 | +- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) installed and logged in |
| 10 | + |
| 11 | +## Background |
| 12 | + |
| 13 | +There are two types of [managed identities](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview#managed-identity-types) that your Azure AKS workload may use: |
| 14 | +- System-assigned managed identity (MSI) |
| 15 | + - Automatically created and managed by Azure at the cluster level. This identity **can not** be shared with other Azure resources. This is used by default. |
| 16 | +- User-assigned managed identity (UAMI) |
| 17 | + - Created and managed by you. Identity **can** be shared with other Azure resources and associated with Kubernetes ServiceAccounts via Azure AD Workload Identity. Requires explicit workload identity configuration (show below). |
| 18 | + |
| 19 | +Since you are using ambient credentials generated by your Azure AKS workload and targeting these credentials for your Command instance, you will need to create an [Azure App Registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). We will walk through App Registration configuration in this document. |
| 20 | + |
| 21 | +## System-Assigned Managed Identity (MSI) |
| 22 | + |
| 23 | +By default, your AKS cluster is configured to use system-assigned managed identity. Your workload should automatically use the identity assigned to the cluster. You will need to set up the scope of the issuer to reference an app registration. Lastly, you will need to make sure the object ID of the managed identity is associated to a security claim in Keyfactor Command. |
| 24 | + |
| 25 | +1. Install `cert-manager` to your AKS cluster. [Installation steps](https://cert-manager.io/docs/installation/helm/) |
| 26 | +1. Install `command-cert-manager-issuer` to your AKS cluster. [Installation steps](../../README.md#installing-command-issuer) |
| 27 | +1. Create an Azure App Registration. [Installation steps](#azure-app-registration) |
| 28 | +1. Deploy Issuer or ClusterIssuer Resource. [Installation steps](../../README.md#creating-issuer-and-clusterissuer-resources) |
| 29 | + - To use ambient credentials, do not supply a `commandSecretName` to your issuer's specification. |
| 30 | + - **IMPORTANT**: Fill in the `scopes` in your issuer's specification with the Application ID URI of your App Registration, suffixed with `./default`. Example: |
| 31 | + ```yaml |
| 32 | + # Example issuer configuration |
| 33 | + spec: |
| 34 | + scopes: "api://your-app-registration-id/.default" |
| 35 | + ``` |
| 36 | +1. Add the system-assigned managed identity object ID to a security claim in Keyfactor Command |
| 37 | + ```bash |
| 38 | + export AKS_CLUSTER_RESOURCE_GROUP="" # the resource group your AKS cluster is deployed to |
| 39 | + export AKS_CLUSTER_NAME="" # the name of your AKS cluster |
| 40 | + export CURRENT_TENANT=$(az account show --query tenantId --output tsv) |
| 41 | + |
| 42 | + echo "AKS Cluster Resource Group: $AKS_CLUSTER_RESOURCE_GROUP" |
| 43 | + echo "AKS Cluster Name: $AKS_CLUSTER_NAME" |
| 44 | + |
| 45 | + # Get the principal ID of your AKS cluster |
| 46 | + AKS_CLUSTER_OBJECT_ID=$(az aks show --resource-group $AKS_CLUSTER_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query "identityProfile.kubeletidentity.objectId" -o tsv) |
| 47 | + echo "AKS Cluster MSI Object ID: $AKS_CLUSTER_OBJECT_ID" |
| 48 | + |
| 49 | + echo "View then OIDC configuration for the Entra OIDC token issuer: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0/.well-known/openid-configuration" |
| 50 | + |
| 51 | + echo "Authority: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0" |
| 52 | + ``` |
| 53 | +
|
| 54 | + > **Note**: AKS workloads inherit the kubelet's managed identity, not the cluster's control plane identity. This is why we use `identityProfile.kubeletidentity.objectId` rather than `identity.principalId`. |
| 55 | + |
| 56 | + You can map the object ID to an OAuth Subject or OAuth Object ID security claim in Keyfactor Command. Make sure the [security claim is associated to a security role](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/SecurityOverview.htm?Highlight=Security%20Roles) with the required permissions. Please refer to the [Configuring Command](../../README.md#configuring-command) **Configure Command Security Roles and Claims** section for security role requirements. |
| 57 | + |
| 58 | + Make sure an identity provider is configured in Keyfactor Command with the authority set to the authority output above. |
| 59 | + |
| 60 | +## User-Assigned Managed Identity (UAMI) |
| 61 | + |
| 62 | +User-assigned managed identity configuration is more involved, but allows the identity to be shared across different AKS clusters. The AKS cluster will need to be configured to allow workload identity and the Command Issuer's ServiceAccount will need to reference the client ID of the user-assigned managed identity. You will need to make sure the principal ID of the user-assigned managed identity is associated to a security claim in Keyfactor Command. |
| 63 | + |
| 64 | +1. Install `cert-manager` to your AKS cluster. [Installation steps](https://cert-manager.io/docs/installation/helm/) |
| 65 | +1. Enable OIDC and Workload Identity on your AKS cluster. [Learn more](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster) |
| 66 | + ```bash |
| 67 | + export AKS_CLUSTER_RESOURCE_GROUP="" # the resource group your AKS cluster is deployed to |
| 68 | + export AKS_CLUSTER_NAME="" # the name of your AKS cluster |
| 69 | +
|
| 70 | + echo "AKS Cluster Resource Group: $AKS_CLUSTER_RESOURCE_GROUP" |
| 71 | + echo "AKS Cluster Name: $AKS_CLUSTER_NAME" |
| 72 | +
|
| 73 | + echo "Enabling OIDC and workload identity on AKS cluster..." |
| 74 | +
|
| 75 | + az aks update \ |
| 76 | + --name ${AKS_CLUSTER_NAME} \ |
| 77 | + --resource-group ${AKS_CLUSTER_RESOURCE_GROUP} \ |
| 78 | + --enable-oidc-issuer \ |
| 79 | + --enable-workload-identity |
| 80 | + ``` |
| 81 | +1. Create a user-assigned managed identity |
| 82 | + ```bash |
| 83 | + export UAMI_IDENTITY_NAME="command-issuer-uami" # the name you want to give your UAMI |
| 84 | +
|
| 85 | + echo "Creating user assigned managed identity $UAMI_IDENTITY_NAME..." |
| 86 | +
|
| 87 | + az identity create --name "${UAMI_IDENTITY_NAME}" --resource-group "${AKS_CLUSTER_RESOURCE_GROUP}" |
| 88 | +
|
| 89 | + export UAMI_CLIENT_ID=$(az identity show --name $UAMI_IDENTITY_NAME --resource-group $AKS_CLUSTER_RESOURCE_GROUP --query clientId --output tsv) |
| 90 | +
|
| 91 | + echo "Client ID of user-assigned managed identity: $UAMI_CLIENT_ID" |
| 92 | + ``` |
| 93 | +1. Deploy Command Cert Manager Issuer with ServiceAccount labeled to use workload identity and UAMI client ID |
| 94 | + |
| 95 | + ```bash |
| 96 | + export UAMI_CLIENT_ID=$(az identity show --name $UAMI_IDENTITY_NAME --resource-group $AKS_CLUSTER_RESOURCE_GROUP --query clientId --output tsv) # should be the same as the previous step |
| 97 | +
|
| 98 | + export ISSUER_NAMESPACE="command-issuer-system" |
| 99 | +
|
| 100 | + echo "Installing command-cert-manager issuer to namespace $ISSUER_NAMESPACE" |
| 101 | + echo "Labeling ServiceAccount to use workload identity with user-assigned-managed-identity client ID $UAMI_CLIENT_ID..." |
| 102 | +
|
| 103 | + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ |
| 104 | + --namespace $ISSUER_NAMESPACE \ |
| 105 | + --create-namespace \ |
| 106 | + --set "fullnameOverride=command-cert-manager-issuer" \ |
| 107 | + --set-string "podLabels.azure\.workload\.identity/use=true" \ |
| 108 | + --set-string "serviceAccount.labels.azure\.workload\.identity/use=true" \ |
| 109 | + --set-string "serviceAccount.annotations.azure\.workload\.identity/client-id=${UAMI_CLIENT_ID}" |
| 110 | + ``` |
| 111 | + |
| 112 | + |
| 113 | + If successful, the Command Issuer Pod will have new environment variables and the Azure WI ServiceAccount token as a projected volume: |
| 114 | + |
| 115 | + ```shell |
| 116 | + kubectl -n command-issuer-system describe pod |
| 117 | + ``` |
| 118 | + |
| 119 | + ```shell |
| 120 | + Containers: |
| 121 | + command-cert-manager-issuer: |
| 122 | + ... |
| 123 | + Environment: |
| 124 | + AZURE_CLIENT_ID: <UAMI_CLIENT_ID> |
| 125 | + AZURE_TENANT_ID: <GUID> |
| 126 | + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token |
| 127 | + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ |
| 128 | + Mounts: |
| 129 | + /var/run/secrets/azure/tokens from azure-identity-token (ro) |
| 130 | + /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-6rmzz (ro) |
| 131 | + ... |
| 132 | + Volumes: |
| 133 | + ... |
| 134 | + azure-identity-token: |
| 135 | + Type: Projected (a volume that contains injected data from multiple sources) |
| 136 | + TokenExpirationSeconds: 3600 |
| 137 | + ``` |
| 138 | +1. Associate a Federated Identity Credential (FIC) with the User Assigned Managed Identity. The FIC allows Command Issuer to act on behalf of the Managed Identity by telling Azure to expect: |
| 139 | + - The `iss` claim of the ServiceAccount token to match the cluster's OIDC Issuer. Azure will also use the Issuer URL to download the JWT signing certificate. |
| 140 | + - The `sub` claim of the ServiceAccount token to match the ServiceAccount's name and namespace. |
| 141 | + |
| 142 | + ```shell |
| 143 | + export SERVICE_ACCOUNT_NAME=command-cert-manager-issuer # This is the default Kubernetes ServiceAccount used by the Command Issuer controller. |
| 144 | + export SERVICE_ACCOUNT_NAMESPACE=command-issuer-system # This is the default namespace for Command Issuer used in this doc. |
| 145 | +
|
| 146 | + export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AKS_CLUSTER_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query "oidcIssuerProfile.issuerUrl" -o tsv) |
| 147 | +
|
| 148 | + echo "Service account issuer: $SERVICE_ACCOUNT_ISSUER" |
| 149 | + echo "Creating federated credentials for user-assigned managed identity $UAMI_IDENTITY_NAME in resource group $AKS_CLUSTER_RESOURCE_GROUP..." |
| 150 | +
|
| 151 | + az identity federated-credential create \ |
| 152 | + --name "${UAMI_IDENTITY_NAME}-federated-credentials" \ |
| 153 | + --identity-name "${UAMI_IDENTITY_NAME}" \ |
| 154 | + --resource-group "${AKS_CLUSTER_RESOURCE_GROUP}" \ |
| 155 | + --issuer "${SERVICE_ACCOUNT_ISSUER}" \ |
| 156 | + --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" \ |
| 157 | + --audiences "api://AzureADTokenExchange" |
| 158 | + ``` |
| 159 | + |
| 160 | + > Read more about [Workload Identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) in the Entra ID documentation. |
| 161 | + > |
| 162 | + > Read more about [the `az identity federated-credential` command](https://learn.microsoft.com/en-us/cli/azure/identity/federated-credential?view=azure-cli-latest). |
| 163 | +1. Create an Azure App Registration. [Installation steps](#azure-app-registration) |
| 164 | +1. Deploy Issuer or ClusterIssuer Resource. [Installation steps](../../README.md#creating-issuer-and-clusterissuer-resources) |
| 165 | + - To use ambient credentials, do not supply a `commandSecretName` to your issuer's specification. |
| 166 | + - **IMPORTANT**: Fill in the `scopes` in your issuer's specification with the Application ID URI of your App Registration, suffixed with `./default`. Example: |
| 167 | + ```yaml |
| 168 | + # Example issuer configuration |
| 169 | + spec: |
| 170 | + scopes: "api://your-app-registration-id/.default" |
| 171 | + ``` |
| 172 | +1. Add the user-assigned managed identity principal ID to a security claim in Keyfactor Command |
| 173 | + ```shell |
| 174 | + export UAMI_PRINCIPAL_ID=$(az identity show --name $UAMI_IDENTITY_NAME --resource-group $AKS_CLUSTER_RESOURCE_GROUP --query principalId --output tsv) |
| 175 | + export CURRENT_TENANT=$(az account show --query tenantId --output tsv) |
| 176 | + echo "UAMI Principal ID: ${UAMI_PRINCIPAL_ID}" |
| 177 | +
|
| 178 | + echo "View then OIDC configuration for the Entra OIDC token issuer: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0/.well-known/openid-configuration" |
| 179 | + |
| 180 | + echo "Authority: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0" |
| 181 | + ``` |
| 182 | + |
| 183 | + You can map the principal ID to an OAuth Subject or OAuth Object ID security claim in Keyfactor Command. Make sure the [security claim is associated to a security role](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/SecurityOverview.htm?Highlight=Security%20Roles) with the required permissions. Please refer to the [Configuring Command](../../README.md#configuring-command) **Configure Command Security Roles and Claims** section for security role requirements. |
| 184 | + |
| 185 | + Make sure an identity provider is configured in Keyfactor Command with the authority set to the authority output above. |
| 186 | + |
| 187 | + |
| 188 | +## Azure App Registration |
| 189 | + |
| 190 | +The identity server that generates the access token from DefaultAzureCredentials requires a valid scope. The access token is being used for authorization on a resource outside of Azure (Keyfactor Command), so an app registration for Entra AD to represent an external application. |
| 191 | + |
| 192 | +Here is official Azure documentation on how to [create an app registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). |
| 193 | + |
| 194 | +After the App Registration is created, expose an API. You can do this by going to Manage > Expose an API and editing the Application ID URI. |
| 195 | + |
| 196 | +> IMPORTANT: The Application ID URI will be used in your `scopes` claim. Make sure to copy this value down. For example, if your Application ID URI is `api://abcd`, your scope value should be `api://abcd/.default`. |
| 197 | + |
| 198 | + |
| 199 | + |
| 200 | +### App Registration Assignment Requirement |
| 201 | + |
| 202 | +By default, Azure App Registrations do not require an assignment in order for an identity to access to the application. However, there may be some compliance need to require an assignment for an identity to access your app registration. This option can be toggled via the Enterprise Application properties of your App Registration. If enabled, and your identity does not have an assignment to this application, you may see the error: |
| 203 | + |
| 204 | +``` |
| 205 | +AADSTS501051: Application '<identity-object-id>'(<identity-name>) is not assigned to a role for the application 'api://<application-id-uri>'(<application-name>) |
| 206 | +``` |
| 207 | + |
| 208 | + |
| 209 | + |
| 210 | +For more information about the assignment requirement for app registrations and how this can affect your identities, please see [this blog post](https://mderriey.com/2019/04/19/aad-apps-user-assignment-required/). |
0 commit comments