paint-brush
Azure DevOps における権利、アクセス許可、アクセス レベルの自動化by@socialdiscoverygroup
12,562
12,562

Azure DevOps における権利、アクセス許可、アクセス レベルの自動化

ビジネスが急速に成長している場合、DevOps エンジニアは、手動アクセス管理を含む、CI/CD に使用されるすべてのシステムのプロセスを自動化するという課題に直面します。このような日常的なタスクにより、顧客の要求の解決が大幅に遅くなります。この記事では、Social Discovery Group チームが、Azure DevOps の権利、アクセス許可、アクセス レベルを自動化するためのカスタマイズされたソリューションを共有しています。
featured image - Azure DevOps における権利、アクセス許可、アクセス レベルの自動化
Social Discovery Group HackerNoon profile picture
0-item

ビジネスが急速に成長している場合、DevOps エンジニアは、手動アクセス管理を含む、CI/CD に使用されるすべてのシステムのプロセスを自動化するという課題に直面します。このような日常的なタスクは、小規模なチームがあり、それぞれ 3 段階のリリースが最大 10 個あり、可変グループが 10 個ある場合には許容される可能性があります。


ただし、チームが数百人に成長し、それぞれに 10 のステージと 200 の変数グループを持つ最大 500 のリリースを手動で操作する必要がある場合を想像してください。この場合、手動でアクセス管理を行うと、顧客のリクエストの解決が大幅に遅くなります。


現段階では、あらゆるビジネスが効率的な自動化の必要性に直面しています。


Social Discovery Group では、主要なクラウド テクノロジとして Azure DevOps を利用しています。ただし、現時点では、権限、アクセス許可、アクセス レベルを自動化する機能がありません。ユーザーのニーズに迅速に対応するために、いくつかのカスタム ソリューションを見つけて権限の割り当てを自動化することにしました。

Azure DevOps で自動化する方法

この記事では、Azure DevOps で特定の日常的なタスクを自動化する方法について、以下の点に焦点を当てていくつかの優れたヒントを共有します。


  • Azure Pipeline を使用して、マスクに基づいて変数グループのグループまたはユーザーにアクセス許可を割り当てます。


  • Azure Pipeline を使用してカタログ グループに分類された、リリース グループ全体の特定のステージにグループまたはユーザーのアクセス許可を割り当てます。

使用されるテクノロジー スタック: Azure サービス、Azure DevOps、Bash、Azure CLI

現在、Azure では、アクセス許可を割り当てるための 3 つのオプション (サイトのグラフィカル インターフェイスの使用、az DevOps CLI の使用、および API 要求の使用) を提供しています。最初のオプションは最も簡単で高速です。ただし、会社が規模を拡大し、CI/CD パイプラインと変数の数が増加するにつれて、権限を手動で割り当てるのは時間がかかります。


権限を付与する 2 番目のオプションは機能が制限されています。言い換えれば、ユーザーは特定のチームが提供するものを超えることはできません。 REST API を介した 3 番目のオプションは、現時点で最も多用途です。複数のステージを持つ多数のリリース パイプラインがある具体的な例を考えてみましょう。


複数のステージからなるリリースパイプライン


複数のステージからなるリリースパイプライン


各展開段階では、特定のユーザー/グループからの許可が必要です (いわゆる展開前承認機能)。ここで問題が発生するのは、特定のユーザーまたはグループを特定の承認者のリストに追加または削除する必要がある場合です。


会社が拡大するにつれて、パイプラインとステージの数が N 倍に増加すると、日常的なケースが問題になり、この問題の解決策は API リクエストを通じてのみ可能になります。私たちの側から実装がどのように見えるかは次のとおりです。


 " pool: vmImage: ubuntu-latest parameters: - name: folder_names type: string default: '\' - name: stage_names type: string default: 'Dev' - name: group_names type: string default: 'Devops' variables: - name: organization value: ORGANIZATION - name: project value: PROJECT - name: pat Value: PAT steps: - bash: | export organization="$(organization)" export project="$(project)" export pat="$(pat)" export folder_names='${{ parameters.folder_names }}' export stage_names='${{ parameters.stage_names }}' export group_names='${{ parameters.group_names }}' export pipeline_name_array=() export stage_name_array=() export group_name_array=() IFS=',' read -ra folder_name_array <<< "$folder_names" IFS=',' read -ra stage_name_array <<< "$stage_names" IFS=',' read -ra group_name_array <<< "$group_names" # Make a GET request to retrieve the release pipelines within the specified project pipeline_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions?api-version=7.0" pipeline_response=$(curl -s -u ":$pat" "$pipeline_url") # Check for errors in the response if [[ "$pipeline_response" == *"error"* ]]; then echo "Error fetching release pipeline data." exit 1 fi pipeline_names=($(echo "$pipeline_response" | jq -r '.value[].name')) pipeline_ids=($(echo "$pipeline_response" | jq -r '.value[].id')) for ((i=0; i<${#pipeline_names[@]}; i++)); do pipeline_name="${pipeline_names[i]}" pipeline_id="${pipeline_ids[i]}" # Make a GET request to retrieve the release pipeline details pipeline_detail_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions/$pipeline_id?api-version=7.0" pipeline_detail_response=$(curl -s -u ":$pat" "$pipeline_detail_url") # Extract the releaseDefinition.path from the pipeline details pipeline_path=$(echo "$pipeline_detail_response" | jq -r '.path') # Check if the pipeline_path matches any of the specified folder_names for folder_name in "${folder_name_array[@]}"; do if [[ "$pipeline_path" == *"$folder_name"* ]]; then # Make a GET request to retrieve all releases for the specified pipeline releases_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/releases?api-version=7.0&definitionId=$pipeline_id" releases_response=$(curl -s -u ":$pat" "$releases_url") # Extract the release names release_names=$(echo "$releases_response" | jq -r '.value[].id') release_definition_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions/$pipeline_id?api-version=7.0" release_definition_response=$(curl -s -u ":$pat" "$release_definition_url") # Iterate through each group name for group_name in "${group_name_array[@]}"; do # Make a GET request to retrieve the list of groups groups_response=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/graph/groups?api-version=7.1-preview.1") # Find the origin_id for the specified group name origin_id=$(echo "$groups_response" | jq -r ".value[] | select(.displayName == \"$group_name\") | .originId") if [ -z "$origin_id" ]; then echo "Group '$group_name' not found or origin_id not available for release '$release_name'." else # Iterate through each stage name for stage_name in "${stage_name_array[@]}"; do # Construct the JSON structure for the new approval new_approval='{ "rank": 1, "isAutomated": false, "isNotificationOn": false, "approver": { "id": "'"$origin_id"'" } }' # Use jq to update the JSON structure for the specified stage updated_definition=$(echo "$release_definition_response" | jq --argjson new_approval "$new_approval" '.environments |= map(if .name == "'"$stage_name"'" then .preDeployApprovals.approvals += [$new_approval] else . end)') # Make a PUT request to update the release definition for the specified stage put_response=$(curl -s -u ":$pat" -X PUT -H "Content-Type: application/json" -d "$updated_definition" "$release_definition_url") release_definition_response=$(curl -s -u ":$pat" "$release_definition_url") # Check if the update was successful if [[ "$put_response" == *"The resource could not be found."* ]]; then echo "Error updating release definition for stage '$stage_name' and group '$group_name'." else echo "Pre-deployment approval added successfully to stage '$stage_name' for group '$group_name' in '$pipeline_id'." fi done fi done fi done done displayName: 'PreDeployApprovals' "


同様の問題は、ライブラリ セクションでも多数の変数グループが作成されるときに発生します。つまり、特定の単語が何かとの所属を示すために使用されます。サービス関連変数との所属を示すために「SERVICES」という単語を含む複数の変数グループが作成される例を詳しく見てみましょう。


また、特定のグループが特定の権限を持つすべての変数にアクセスする必要があるシナリオも検討します。


変数グループが多数ある場合のライブラリセクション


この一般的なケースの解決策は、REST API を介してのみ可能です。


 " variables: - name: organization value: ORGANIZATION - name: project value: PROJECT - name: pat value: PAT parameters: - name: searchWord type: string default: 'SERVICES' - name: UserOrGroupName type: string default: 'Devops' - name: UserRights type: string default: 'Administrator' steps: - bash: | export organization="$(organization)" export project="$(project)" export pat="$(pat)" export userOrGroupName='${{ parameters.UserOrGroupName }}' export UserRights='${{ parameters.UserRights }}' export searchWord='${{ parameters.searchWord }}' # Perform the API request and store the JSON response in a variable response=$(curl -s -u ":$pat" "https://dev.azure.com/$organization/$project/_apis/distributedtask/variablegroups?api-version=6.0") # Initialize an empty array to store matching JSON objects matching_json=() # Loop through the JSON objects and append matching objects to the array while read -r json; do if [[ $(echo "$json" | jq -r '.name' | grep "$searchWord") ]]; then matching_json+=("$json") fi done < <(echo "$response" | jq -c '.value[]') # Iterate through the matching variable groups and assign permissions for group in "${matching_json[@]}"; do # Extract the variable group ID and name variableGroupId=$(echo "$group" | jq -r '.id') variableGroupName=$(echo "$group" | jq -r '.name') # Determine the type of userOrGroupName (username or group name) if [[ $myString != *'@'* ]]; then # If userOrGroupName matches the username format, it's treated as a username roleName=$UserRights assignType="group" groupsResponse=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/graph/groups?api-version=6.0-preview.1") #groupOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].originId') groupOriginId=$(echo "$groupsResponse" | jq -r ".value[] | select(.displayName == \"$userOrGroupName\") | .originId") # Get the user's originId using Azure DevOps REST API #userOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].principalName') else # Otherwise, it's treated as a group name roleName=$UserRights assignType="user" userOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].principalName') # Get the group's originId using Azure DevOps REST API #groupOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].originId') fi # Construct the API URL for assigning permissions apiUrl="https://dev.azure.com/$organization/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/19802563-1b7b-43d6-81e0-16cf29d68c0d$"$variableGroupId"?api-version=5.1-preview" # Assign permissions based on the userOrGroupName type if [ "$assignType" == "group" ]; then # Assign permissions to a group # Construct the request body with group's originId requestBody="[{ \"roleName\": \"$roleName\", \"userId\": \"$groupOriginId\" }]" else # Assign permissions to a user # Construct the request body with the user's uniqueName requestBody="[{ \"roleName\": \"$roleName\", \"uniqueName\": \"$userOrGroupName\" }]" fi # Execute the REST API call to assign permissions response=$(curl -s -u ":$pat" -X PUT -H "Content-Type: application/json" -d "$requestBody" "$apiUrl") # Check if the permissions were successfully assigned if [[ "$response" == *"error"* ]]; then echo "Error assigning permissions to $assignType '$userOrGroupName' in variable group '$variableGroupName'." else echo "Permissions assigned successfully to $assignType '$userOrGroupName' in variable group '$variableGroupName'." fi done displayName: 'variable-groups' "


これらのプロセスを自動化することで、Azure DevOps の権利とアクセス許可の構造をより詳細に理解できるようになり、プロジェクトのセキュリティが強化されました。


現在、幅広いタスクに対応する安定した多用途のパイプラインがあり、ユーザー リクエストの処理時間を短縮し、実行品質を向上させています。


さらに、日常業務から離れて他の CI/CD タスクにより多くの時間を割り当てたため、システム全体の開発速度と品質が向上しました。


私たちは、日常業務の自動化、さまざまな実装オプションの調査と分析、デバッグ、改良に多大な努力を払ってきました。既製のパイプラインとスクリプトがあると、数秒でタスクを完了できるため、非常に便利です。