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개의 단계와 200개의 변수 그룹이 있는 최대 500개의 릴리스를 수동으로 작업해야 하는 수백 명의 사용자로 성장한다고 상상해 보십시오. 이 경우 수동 액세스 관리로 인해 고객 요청 해결 속도가 크게 느려집니다.


이 단계에서 모든 비즈니스는 효율적인 자동화가 필요합니다.


Social Discovery Group에서는 Azure DevOps를 기본 클라우드 기술로 사용합니다. 그러나 현재는 권한, 사용 권한 및 액세스 수준을 자동화하는 기능이 부족합니다. 사용자 요구 사항을 신속하게 해결하기 위해 몇 가지 맞춤형 솔루션을 찾고 권한 할당을 자동화하기로 결정했습니다.

Azure DevOps에서 자동화하는 방법

이 문서에서는 다음 사항에 중점을 두고 Azure DevOps에서 특정 일상적인 작업을 자동화하는 방법에 대한 몇 가지 유용한 정보를 공유합니다.


  • Azure Pipeline을 사용하여 마스크를 기반으로 변수 그룹에 대한 그룹 또는 사용자에게 권한을 할당합니다.


  • Azure Pipeline을 사용하여 카탈로그 그룹으로 분류된 전체 릴리스 그룹의 특정 단계에 그룹 또는 사용자 권한을 할당합니다.

사용된 기술 스택: Azure 서비스, Azure DevOps, Bash, Azure CLI

현재 Azure는 권한 할당을 위해 사이트의 그래픽 인터페이스 사용, az DevOps CLI 사용 및 API 요청이라는 세 가지 옵션을 제공합니다. 첫 번째 옵션은 가장 간단하고 빠릅니다. 그러나 회사가 규모를 확장하고 CI/CD 파이프라인 및 변수의 수가 증가함에 따라 권한을 수동으로 할당하는 데 시간이 많이 걸립니다.


권한을 부여하는 두 번째 옵션은 기능이 제한되어 있습니다. 즉, 사용자는 특정 팀이 제공하는 것 이상을 사용할 수 없습니다. REST API를 통한 세 번째 옵션은 현재 가장 다재다능합니다. 여러 단계로 구성된 수많은 릴리스 파이프라인이 있는 구체적인 예를 고려해 보겠습니다.


여러 단계가 포함된 릴리스 파이프라인


여러 단계가 포함된 릴리스 파이프라인


각 배포 단계에는 특정 사용자/그룹의 권한이 필요합니다(배포 전 승인 기능이라고도 함). 특정 사용자나 그룹을 특정 승인자 목록에 추가하거나 제거해야 할 때 여기에 문제가 나타납니다.


회사가 확장되면서 파이프라인과 스테이지의 수가 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 작업에 더 많은 시간을 할당함으로써 전반적인 시스템 개발 속도와 품질이 향상되었습니다.


우리는 일상적인 작업을 자동화하고, 다양한 구현 옵션을 탐색 및 분석하고, 디버그 및 개선하기 위해 상당한 노력을 기울였습니다. 이미 만들어진 파이프라인과 스크립트를 가지고 있으면 몇 초 만에 작업을 완료할 수 있으므로 매우 편리합니다.