From fd8d3cf7f1e9ccc5622a94e14c4e0ea402705bef Mon Sep 17 00:00:00 2001 From: Nicolas Ontiveros <54044510+niontive@users.noreply.github.com> Date: Wed, 21 Aug 2024 06:25:17 -0700 Subject: [PATCH] Add scripts to create platform identities for dev (#3734) --------- Co-authored-by: Nicolas Ontiveros --- docs/deploy-development-rp.md | 5 +- hack/devtools/local_dev_env.sh | 177 +++++++++++++++++++++++++++++++-- hack/devtools/msi.sh | 31 ++++-- 3 files changed, 196 insertions(+), 17 deletions(-) diff --git a/docs/deploy-development-rp.md b/docs/deploy-development-rp.md index 7cfca79aa18..4696e84e910 100644 --- a/docs/deploy-development-rp.md +++ b/docs/deploy-development-rp.md @@ -60,8 +60,8 @@ It uses hacks scripts around a lot of the setup to make things easier to bootstr SECRET_SA_ACCOUNT_NAME=rharosecretsdev make secrets ``` -1. Run [msi.sh](../hack/devtools/msi.sh) to create a service principal and self-signed certificate to - mock a cluster MSI. Save the output values for `Client ID`, `Base64 Encoded Certificate`, and `Tenant`. +1. Run [msi.sh](../hack/devtools/msi.sh) to create a service principal and self-signed certificate to +mock a cluster MSI. This script will also create the platform identities, platform identity role assignments, and role assignment on mock cluster MSI to federate the platform identities. Platform identities will be created in resource group `RESOURCEGROUP` and subscription `SUBSCRIPTION`. Save the output values for cluster MSI `Client ID`, `Base64 Encoded Certificate`, and `Tenant`. Additionally, save the value for `Platform workload identity role sets`. 1. Copy, edit (if necessary) and source your environment file. The required environment variable configuration is documented immediately below: @@ -79,6 +79,7 @@ It uses hacks scripts around a lot of the setup to make things easier to bootstr - `MOCK_MSI_CLIENT_ID`: Client ID for service principal that mocks cluster MSI (see previous step). - `MOCK_MSI_CERT`: Base64 encoded certificate for service principal that mocks cluster MSI (see previous step). - `MOCK_MSI_TENANT_ID`: Tenant ID for service principal that mocks cluster MSI (see previous step). + - `PLATFORM_WORKLOAD_IDENTITY_ROLE_SETS`: The platform workload identity role sets (see previous step or value in `local_dev_env.sh`). 1. Create your own RP database: diff --git a/hack/devtools/local_dev_env.sh b/hack/devtools/local_dev_env.sh index 3a9ea8487af..4d1a8c39013 100755 --- a/hack/devtools/local_dev_env.sh +++ b/hack/devtools/local_dev_env.sh @@ -1,4 +1,7 @@ #!/bin/bash +set -o errexit +set -o nounset +set -o pipefail # Local development environment script. # Execute this script from the root folder of the repo (ARO-RP). @@ -7,6 +10,71 @@ # The steps here are the ones defined in docs/deploy-development-rp.md # We recommend to use this script after you understand the steps of the process, not before. +PLATFORM_WORKLOAD_IDENTITY_ROLE_SETS='[ + { + "openShiftVersion": "4.14", + "platformWorkloadIdentityRoles": [ + { + "operatorName": "CloudControllerManager", + "roleDefinitionName": "Azure Red Hat OpenShift Cloud Controller Manager Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + "serviceAccounts": ["openshift-cloud-controller-manager:cloud-controller-manager"] + }, + { + "operatorName": "ClusterIngressOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Cluster Ingress Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + "serviceAccounts": ["openshift-ingress-operator:ingress-operator"] + }, + { + "operatorName": "MachineApiOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Machine API Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/0358943c-7e01-48ba-8889-02cc51d78637", + "serviceAccounts": ["openshift-machine-api:machine-api-operator"] + }, + { + "operatorName": "StorageOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Storage Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/5b7237c5-45e1-49d6-bc18-a1f62f400748", + "serviceAccounts": [ + "openshift-cluster-csi-drivers:azure-disk-csi-driver-operator", + "openshift-cluster-csi-drivers:azure-disk-csi-driver-controller-sa" + ] + }, + { + "operatorName": "NetworkOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Network Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/be7a6435-15ae-4171-8f30-4a343eff9e8f", + "serviceAccounts": ["openshift-cloud-network-config-controller:cloud-network-config-controller"] + }, + { + "operatorName": "ImageRegistryOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Image Registry Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/8b32b316-c2f5-4ddf-b05b-83dacd2d08b5", + "serviceAccounts": [ + "openshift-image-registry:cluster-image-registry-operator", + "openshift-image-registry:registry" + ] + }, + { + "operatorName": "AzureFilesStorageOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Azure Files Storage Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/0d7aedc0-15fd-4a67-a412-efad370c947e", + "serviceAccounts": [ + "openshift-cluster-csi-drivers:azure-file-csi-driver-operator", + "openshift-cluster-csi-drivers:azure-file-csi-driver-controller-sa", + "openshift-cluster-csi-drivers:azure-file-csi-driver-node-sa" + ] + }, + { + "operatorName": "ServiceOperator", + "roleDefinitionName": "Azure Red Hat OpenShift Service Operator Role", + "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/4436bae4-7702-4c84-919b-c4069ff25ee2", + "serviceAccounts": ["openshift-azure-operator:aro-operator-master"] + } + ] + } +]' build_development_az_aro_extension() { echo "INFO: Building development az aro extension..." @@ -28,7 +96,7 @@ set_storage_account() { ask_to_create_default_env_config() { local answer - read -p "Do you want to create a default env file? (existing one will be overwritten, if any) (y / n) " answer + read -r -p "Do you want to create a default env file? (existing one will be overwritten, if any) (y / n) " answer if [[ "$answer" == "y" || "$answer" == "Y" ]]; then create_env_file @@ -42,7 +110,7 @@ ask_to_create_default_env_config() { # We use a service principal and certificate as the mock MSI object create_mock_msi() { appName="mock-msi-$(openssl rand -base64 9 | tr -dc 'a-zA-Z0-9' | head -c 6)" - az ad sp create-for-rbac --name $appName --create-cert --output json + az ad sp create-for-rbac --name "$appName" --create-cert --output json } get_mock_msi_clientID() { @@ -55,14 +123,14 @@ get_mock_msi_tenantID() { get_mock_msi_cert() { certFilePath=$(echo "$1" | jq -r '.fileWithCertAndPrivateKey') - base64EncodedCert=$(base64 -w 0 $certFilePath) - rm $certFilePath - echo $base64EncodedCert + base64EncodedCert=$(base64 -w 0 "$certFilePath") + rm "$certFilePath" + echo "$base64EncodedCert" } create_env_file() { local answer - read -p "Do you want to create an env file for Managed/Workload identity development? " answer + read -r -p "Do you want to create an env file for Managed/Workload identity development? (y / n) " answer if [[ "$answer" == "y" || "$answer" == "Y" ]]; then create_miwi_env_file else @@ -70,6 +138,95 @@ create_env_file() { fi } +get_platform_workloadIdentity_role_sets() { + # Parse the JSON data using jq + platformWorkloadIdentityRoles=$(echo "${PLATFORM_WORKLOAD_IDENTITY_ROLE_SETS}" | jq -c '.[].platformWorkloadIdentityRoles[]') + + echo "${platformWorkloadIdentityRoles}" +} + +assign_role_to_identity() { + local objectId=$1 + local roleId=$2 + local scope="/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${RESOURCEGROUP}" + local roles + + if ! roles=$(az role assignment list --assignee "${objectId}" --role "${roleId}" --scope "${scope}" 2>/dev/null); then + # If the role assignment list fails, we assume the identity is newly created and we can proceed with the role assignment + echo "INFO: Unable to list role assignments for identity: ${objectId}" + fi + + if [ "$roles" == "" ] || [ "$roles" == "[]" ] ; then + echo "INFO: Assigning role to identity: ${objectId}" + az role assignment create --assignee-object-id "${objectId}" --assignee-principal-type "ServicePrincipal" --role "${roleId}" --scope "${scope}" --output json + echo "" + else + echo "INFO: Role already assigned to identity: ${objectId}" + echo "" + fi +} + +create_platform_identity_and_assign_role() { + local operatorName="${1}" + local roleDefinitionId="${2}" + local identityName="aro-${operatorName}" + local identity + + if ! identity=$(az identity show --name "${identityName}" --resource-group "${RESOURCEGROUP}" --subscription "${AZURE_SUBSCRIPTION_ID}" --output json 2>/dev/null); then + echo "INFO: Creating platform identity for operator: ${operatorName}" + identity=$(az identity create --name "${identityName}" --resource-group "${RESOURCEGROUP}" --subscription "${AZURE_SUBSCRIPTION_ID}" --output json) + fi + + # Extract the client ID, principal Id, resource ID and name from the result + clientID=$(jq -r .clientId <<<"${identity}") + principalId=$(jq -r .principalId <<<"${identity}") + resourceId=$(jq -r .id <<<"${identity}") + name=$(jq -r .name <<<"${identity}") + + echo "Client ID: $clientID" + echo "Principal ID: $principalId" + echo "Resource ID: $resourceId" + echo "Name: $name" + echo "" + + # The following operators require a role assignment since they need access to customer BYO virtual network + # RP will be responsible for other role assignments + if [[ "${operatorName}" == "MachineApiOperator" || "${operatorName}" == "NetworkOperator" \ + || "${operatorName}" == "AzureFilesStorageOperator" || "${operatorName}" == "ServiceOperator" ]]; then + + assign_role_to_identity "${principalId}" "${roleDefinitionId}" + fi +} + +setup_platform_identity() { + local platformWorkloadIdentityRoles + + platformWorkloadIdentityRoles=$(get_platform_workloadIdentity_role_sets) + + echo "INFO: Creating platform identities under RG ($RESOURCEGROUP) and Sub Id ($AZURE_SUBSCRIPTION_ID)" + echo "" + + # Loop through each element under platformWorkloadIdentityRoles + while read -r role; do + operatorName=$(echo "$role" | jq -r '.operatorName') + roleDefinitionId=$(echo "$role" | jq -r '.roleDefinitionId' | awk -F'/' '{print $NF}') + + create_platform_identity_and_assign_role "${operatorName}" "${roleDefinitionId}" + + done <<< "$platformWorkloadIdentityRoles" +} + +cluster_msi_role_assignment() { + local clusterMSIAppID="${1}" + local FEDERATED_CREDENTIAL_ROLE_ID="ef318e2a-8334-4a05-9e4a-295a196c6a6e" + local clusterMSIObjectID + + clusterMSIObjectID=$(az ad sp show --id "${clusterMSIAppID}" --query '{objectId: id}' | jq -r .objectId) + + echo "INFO: Assigning role to cluster MSI: ${clusterMSIAppID}" + assign_role_to_identity "${clusterMSIObjectID}" "${FEDERATED_CREDENTIAL_ROLE_ID}" +} + create_miwi_env_file() { echo "INFO: Creating default env config file for managed/workload identity development..." @@ -78,6 +235,9 @@ create_miwi_env_file() { mockTenantID=$(get_mock_msi_tenantID "$mockMSI") mockCert=$(get_mock_msi_cert "$mockMSI") + setup_platform_identity + cluster_msi_role_assignment "${mockClientID}" + cat >env <