Skip to content

Commit 8fcd59b

Browse files
CopilotEMaher
andauthored
feat: replace service principal with managed identity + WIF in AzDo init
Migrate Azure DevOps identity setup from service principal with password to user-assigned managed identity (UAMI) + workload identity federation (WIF) — no stored credentials. - identity-setup-azdo-prompt.ts: az identity create + az devops invoke with WorkloadIdentityFederation scheme + az identity federated-credential create (bash-only Copilot prompt) - identity-guide-service.ts: same migration with PowerShell + bash - Tests updated: remove SP/password assertions, add MI/WIF assertions - 879 tests pass, lint clean Agent-Logs-Url: https://github.com/Azure/apiops-cli/sessions/c53bd288-2277-4a81-be60-e7685ddd634f Co-authored-by: EMaher <9244742+EMaher@users.noreply.github.com>
1 parent 6b6999c commit 8fcd59b

5 files changed

Lines changed: 241 additions & 122 deletions

File tree

‎src/services/identity-guide-service.ts‎

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -160,40 +160,51 @@ Test the authentication by running a workflow manually or pushing to main branch
160160
\`\`\`powershell
161161
$SUBSCRIPTION_ID = "${subscriptionId}"
162162
$RESOURCE_GROUP = "${resourceGroup}"
163-
$APP_NAME = "apiops-azdo-sp"
163+
$MI_NAME = "apiops-azdo-mi"
164+
$MI_RESOURCE_GROUP = "<your-mi-resource-group>"
164165
$ENVIRONMENTS = @(${environmentsArrayPowerShell})
165166
\`\`\`
166167
167168
**Git Bash:**
168169
\`\`\`bash
169170
SUBSCRIPTION_ID="${subscriptionId}"
170171
RESOURCE_GROUP="${resourceGroup}"
171-
APP_NAME="apiops-azdo-sp"
172+
MI_NAME="apiops-azdo-mi"
173+
MI_RESOURCE_GROUP="<your-mi-resource-group>"
172174
ENVIRONMENTS=(${environmentsArrayBash})
173175
\`\`\`
174176
175177
---
176178
177-
## Step 2: Create Service Principal
179+
## Step 2: Create Managed Identity
178180
179181
**PowerShell:**
180182
\`\`\`powershell
181-
$SP_OUTPUT = az ad sp create-for-rbac --name $APP_NAME --role "API Management Service Contributor" --scopes "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
182-
$spObj = $SP_OUTPUT | ConvertFrom-Json
183-
$APP_ID = $spObj.appId
184-
$PASSWORD = $spObj.password
185-
$TENANT_ID = $spObj.tenant
183+
# Create user-assigned managed identity (no password)
184+
az identity create --name $MI_NAME --resource-group $MI_RESOURCE_GROUP
185+
$MI_CLIENT_ID = az identity show --name $MI_NAME --resource-group $MI_RESOURCE_GROUP --query clientId -o tsv
186+
$MI_PRINCIPAL_ID = az identity show --name $MI_NAME --resource-group $MI_RESOURCE_GROUP --query principalId -o tsv
187+
$TENANT_ID = az account show --query tenantId -o tsv
188+
Write-Host "Managed Identity Client ID: $MI_CLIENT_ID"
189+
Write-Host "Managed Identity Principal ID: $MI_PRINCIPAL_ID"
190+
191+
# Assign API Management Service Contributor role
192+
az role assignment create --assignee-object-id $MI_PRINCIPAL_ID --assignee-principal-type ServicePrincipal --role "API Management Service Contributor" --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
186193
\`\`\`
187194
188-
**Git Bash:** (use \`MSYS_NO_PATHCONV=1\` to prevent path conversion on Windows)
195+
**Git Bash:**
189196
\`\`\`bash
190-
SP_OUTPUT=$(MSYS_NO_PATHCONV=1 az ad sp create-for-rbac --name "$APP_NAME" --role "API Management Service Contributor" --scopes "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP")
191-
APP_ID=$(echo "$SP_OUTPUT" | grep -o '"appId": *"[^"]*"' | cut -d'"' -f4)
192-
PASSWORD=$(echo "$SP_OUTPUT" | grep -o '"password": *"[^"]*"' | cut -d'"' -f4)
193-
TENANT_ID=$(echo "$SP_OUTPUT" | grep -o '"tenant": *"[^"]*"' | cut -d'"' -f4)
194-
\`\`\`
197+
# Create user-assigned managed identity (no password)
198+
az identity create --name "$MI_NAME" --resource-group "$MI_RESOURCE_GROUP"
199+
MI_CLIENT_ID=$(az identity show --name "$MI_NAME" --resource-group "$MI_RESOURCE_GROUP" --query clientId -o tsv)
200+
MI_PRINCIPAL_ID=$(az identity show --name "$MI_NAME" --resource-group "$MI_RESOURCE_GROUP" --query principalId -o tsv)
201+
TENANT_ID=$(az account show --query tenantId -o tsv)
202+
echo "Managed Identity Client ID: $MI_CLIENT_ID"
203+
echo "Managed Identity Principal ID: $MI_PRINCIPAL_ID"
195204
196-
**Important:** The password is only shown once during creation. Save it securely now!
205+
# Assign API Management Service Contributor role
206+
az role assignment create --assignee-object-id "$MI_PRINCIPAL_ID" --assignee-principal-type ServicePrincipal --role "API Management Service Contributor" --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
207+
\`\`\`
197208
198209
---
199210
@@ -230,52 +241,67 @@ SUBSCRIPTION_NAME=$(az account show --subscription "$SUBSCRIPTION_ID" --query na
230241
231242
## Step 4: Create Azure Service Connections
232243
233-
Set the service principal key for non-interactive creation:
244+
Create service connections using workload identity federation:
234245
235246
**PowerShell:**
236247
\`\`\`powershell
237-
$env:AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY = $PASSWORD
238-
\`\`\`
239-
240-
**Git Bash:**
241-
\`\`\`bash
242-
export AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY="$PASSWORD"
243-
\`\`\`
244-
245-
Create the base service connection and one per environment:
248+
$SUBSCRIPTION_NAME = az account show --subscription $SUBSCRIPTION_ID --query name -o tsv
246249
247-
**PowerShell:**
248-
\`\`\`powershell
249-
az devops service-endpoint azurerm create --name "AZURE_SERVICE_CONNECTION" --azure-rm-service-principal-id $APP_ID --azure-rm-subscription-id $SUBSCRIPTION_ID --azure-rm-subscription-name $SUBSCRIPTION_NAME --azure-rm-tenant-id $TENANT_ID
250+
function New-WifServiceConnection {
251+
param($SC_NAME)
252+
$body = @{
253+
name = $SC_NAME; type = "azurerm"; url = "https://management.azure.com/"
254+
authorization = @{ scheme = "WorkloadIdentityFederation"; parameters = @{ servicePrincipalId = $MI_CLIENT_ID; tenantid = $TENANT_ID } }
255+
data = @{ subscriptionId = $SUBSCRIPTION_ID; subscriptionName = $SUBSCRIPTION_NAME; environment = "AzureCloud"; scopeLevel = "Subscription"; creationMode = "Manual" }
256+
} | ConvertTo-Json -Depth 10 -Compress
257+
$body | Out-File -Encoding utf8 sc-body.json
258+
$ep = az devops invoke --area serviceEndpoint --resource endpoints --route-parameters project=$AZDO_PROJECT --http-method POST --api-version "7.1" --in-file sc-body.json | ConvertFrom-Json
259+
Remove-Item sc-body.json -ErrorAction SilentlyContinue
260+
$issuer = $ep.authorization.parameters.workloadIdentityFederationIssuer
261+
$subject = $ep.authorization.parameters.workloadIdentityFederationSubject
262+
$credName = $SC_NAME.ToLower().Replace("_", "-")
263+
az identity federated-credential create --name "azdo-$credName" --identity-name $MI_NAME --resource-group $MI_RESOURCE_GROUP --issuer $issuer --subject $subject --audiences "api://AzureADTokenExchange"
264+
Write-Host "Created service connection: $SC_NAME"
265+
}
250266
267+
New-WifServiceConnection "AZURE_SERVICE_CONNECTION"
251268
foreach ($env in $ENVIRONMENTS) {
252269
$envUpper = $env.ToUpper()
253-
az devops service-endpoint azurerm create --name "AZURE_SERVICE_CONNECTION_$envUpper" --azure-rm-service-principal-id $APP_ID --azure-rm-subscription-id $SUBSCRIPTION_ID --azure-rm-subscription-name $SUBSCRIPTION_NAME --azure-rm-tenant-id $TENANT_ID
270+
New-WifServiceConnection "AZURE_SERVICE_CONNECTION_$envUpper"
254271
}
255272
\`\`\`
256273
257274
**Git Bash:**
258275
\`\`\`bash
259-
az devops service-endpoint azurerm create --name "AZURE_SERVICE_CONNECTION" --azure-rm-service-principal-id "$APP_ID" --azure-rm-subscription-id "$SUBSCRIPTION_ID" --azure-rm-subscription-name "$SUBSCRIPTION_NAME" --azure-rm-tenant-id "$TENANT_ID"
276+
SUBSCRIPTION_NAME=$(az account show --subscription "$SUBSCRIPTION_ID" --query name -o tsv)
277+
278+
create_wif_service_connection() {
279+
local SC_NAME="$1"
280+
ENDPOINT_JSON=$(az devops invoke \\
281+
--area serviceEndpoint --resource endpoints \\
282+
--route-parameters project="$AZDO_PROJECT" \\
283+
--http-method POST --api-version "7.1" \\
284+
--in-file - << ENDJSON
285+
{"name":"$SC_NAME","type":"azurerm","url":"https://management.azure.com/","authorization":{"scheme":"WorkloadIdentityFederation","parameters":{"servicePrincipalId":"$MI_CLIENT_ID","tenantid":"$TENANT_ID"}},"data":{"subscriptionId":"$SUBSCRIPTION_ID","subscriptionName":"$SUBSCRIPTION_NAME","environment":"AzureCloud","scopeLevel":"Subscription","creationMode":"Manual"}}
286+
ENDJSON
287+
)
288+
ISSUER=$(echo "$ENDPOINT_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['authorization']['parameters']['workloadIdentityFederationIssuer'])")
289+
SUBJECT=$(echo "$ENDPOINT_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['authorization']['parameters']['workloadIdentityFederationSubject'])")
290+
CRED_NAME=$(echo "$SC_NAME" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
291+
az identity federated-credential create \\
292+
--name "azdo-$CRED_NAME" --identity-name "$MI_NAME" --resource-group "$MI_RESOURCE_GROUP" \\
293+
--issuer "$ISSUER" --subject "$SUBJECT" \\
294+
--audiences "api://AzureADTokenExchange"
295+
echo "Created service connection: $SC_NAME"
296+
}
260297
298+
create_wif_service_connection "AZURE_SERVICE_CONNECTION"
261299
for env in "\${ENVIRONMENTS[@]}"; do
262300
env_upper=$(echo "$env" | tr '[:lower:]' '[:upper:]')
263-
az devops service-endpoint azurerm create --name "AZURE_SERVICE_CONNECTION_$env_upper" --azure-rm-service-principal-id "$APP_ID" --azure-rm-subscription-id "$SUBSCRIPTION_ID" --azure-rm-subscription-name "$SUBSCRIPTION_NAME" --azure-rm-tenant-id "$TENANT_ID"
301+
create_wif_service_connection "AZURE_SERVICE_CONNECTION_$env_upper"
264302
done
265303
\`\`\`
266304
267-
Clean up the environment variable:
268-
269-
**PowerShell:**
270-
\`\`\`powershell
271-
Remove-Item Env:AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY
272-
\`\`\`
273-
274-
**Git Bash:**
275-
\`\`\`bash
276-
unset AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY
277-
\`\`\`
278-
279305
Verify (works in both shells):
280306
\`\`\`bash
281307
az devops service-endpoint list --query "[].name" -o table
@@ -481,12 +507,12 @@ az devops invoke --area environments --resource environments --route-parameters
481507
482508
**PowerShell:**
483509
\`\`\`powershell
484-
az role assignment list --assignee $APP_ID --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
510+
az role assignment list --assignee $MI_CLIENT_ID --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
485511
\`\`\`
486512
487513
**Git Bash:**
488514
\`\`\`bash
489-
az role assignment list --assignee "$APP_ID" --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
515+
az role assignment list --assignee "$MI_CLIENT_ID" --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
490516
\`\`\`
491517
492518
**Final Test:** Run the extract pipeline manually to verify end-to-end authentication and permissions.
@@ -543,10 +569,10 @@ az pipelines run --name "apiops-extract"
543569
---
544570
545571
## Security Notes
546-
- Use separate service principals for production environments
572+
- Use separate service connections for production environments
547573
- Enable environment approvals for production deployments
548-
- Rotate service principal secrets periodically (recommended: 90 days)
549-
- Use managed identities when possible for Azure-hosted agents
574+
- User-assigned managed identities have no passwords or secrets to rotate — credentials-free
575+
- Federated credentials are tied to specific Azure DevOps service connections — review and rotate if service connections are recreated
550576
- Review RBAC assignments regularly
551577
`;
552578
}

‎src/templates/copilot/identity-setup-azdo-prompt.ts‎

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* GitHub Copilot prompt template for automating Azure DevOps identity setup.
33
* Generates a .prompt.md file that guides Copilot through:
44
* 1. Gathering Azure & Azure DevOps info from the user
5-
* 2. Creating Azure AD service principal
5+
* 2. Creating user-assigned managed identity
66
* 3. Configuring Azure DevOps CLI
77
* 4. Creating service connections
88
* 5. Creating variable groups
@@ -22,15 +22,8 @@ export function generateIdentitySetupAzdoPrompt(config: IdentitySetupAzdoPromptC
2222
).join('\n');
2323

2424
const envServiceConnections = config.environments.map((env) =>
25-
`# Create service connection for ${env} environment
26-
env_upper=$(echo "${env}" | tr '[:lower:]' '[:upper:]')
27-
az devops service-endpoint azurerm create \\
28-
--name "AZURE_SERVICE_CONNECTION_\${env_upper}" \\
29-
--azure-rm-service-principal-id "$APP_ID" \\
30-
--azure-rm-subscription-id "$SUBSCRIPTION_ID" \\
31-
--azure-rm-subscription-name "$SUBSCRIPTION_NAME" \\
32-
--azure-rm-tenant-id "$TENANT_ID"`
33-
).join('\n\n');
25+
`create_wif_service_connection "AZURE_SERVICE_CONNECTION_${env.toUpperCase()}"`
26+
).join('\n');
3427

3528
const envVariableGroups = config.environments.map((env) =>
3629
`# Create variable group for ${env} environment
@@ -72,9 +65,9 @@ EOF`
7265
7366
## Goal
7467
75-
Configure Azure service principal and Azure DevOps service connections, variable
68+
Configure a user-assigned managed identity and Azure DevOps service connections, variable
7669
groups, and environments so the APIOps extract and publish pipelines can
77-
authenticate to Azure and deploy to each environment.
70+
authenticate to Azure using workload identity federation — no stored secrets or passwords.
7871
7972
---
8073
@@ -146,35 +139,43 @@ each answer for use in later steps.
146139
|----------|-------------|---------|
147140
| \`SUBSCRIPTION_ID\` | Azure subscription ID (used for all environments) | \`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\` |
148141
| \`RESOURCE_GROUP\` | Resource group prefix or base name | \`rg-apim\` |
149-
| \`APP_NAME\` | Display name for the service principal | \`apiops-azdo-sp\` |
142+
| \`MI_NAME\` | Display name for the managed identity | \`apiops-azdo-mi\` |
143+
| \`MI_RESOURCE_GROUP\` | Resource group where the managed identity will be created | \`rg-apiops-mi\` |
150144
| \`AZDO_ORG\` | Azure DevOps organization URL | \`https://dev.azure.com/my-org\` |
151145
| \`ORG_NAME\` | Short organization name (for Build Service) | \`my-org\` |
152146
| \`AZDO_PROJECT\` | Azure DevOps project name | \`apim-project\` |
153147
${envGatherTable}
154148
155149
---
156150
157-
## Step 2 — Create Service Principal
151+
## Step 2 — Create Managed Identity
158152
159-
> ⚠️ **Error Handling:** If any command fails, stop immediately and show the user the full error output verbatim. Do NOT retry silently. Common issues include insufficient permissions (requires Contributor role on the subscription or resource group).
153+
> ⚠️ **Error Handling:** If any command fails, stop immediately and show the user the full error output verbatim. Do NOT retry silently. Common issues include insufficient permissions (requires Contributor role on the resource group).
160154
161155
\`\`\`bash
162-
# Create service principal with API Management Service Contributor role
163-
SP_OUTPUT=$(az ad sp create-for-rbac \\
164-
--name "\${APP_NAME}" \\
156+
# Create user-assigned managed identity (no password — credentials-free)
157+
az identity create \\
158+
--name "\${MI_NAME}" \\
159+
--resource-group "\${MI_RESOURCE_GROUP}"
160+
161+
# Retrieve managed identity properties
162+
MI_CLIENT_ID=$(az identity show --name "\${MI_NAME}" --resource-group "\${MI_RESOURCE_GROUP}" --query clientId -o tsv)
163+
MI_PRINCIPAL_ID=$(az identity show --name "\${MI_NAME}" --resource-group "\${MI_RESOURCE_GROUP}" --query principalId -o tsv)
164+
TENANT_ID=$(az account show --query tenantId -o tsv)
165+
166+
echo "Managed Identity Client ID: $MI_CLIENT_ID"
167+
echo "Managed Identity Principal ID: $MI_PRINCIPAL_ID"
168+
echo "Tenant ID: $TENANT_ID"
169+
170+
# Assign API Management Service Contributor role to the managed identity
171+
az role assignment create \\
172+
--assignee-object-id "\${MI_PRINCIPAL_ID}" \\
173+
--assignee-principal-type ServicePrincipal \\
165174
--role "API Management Service Contributor" \\
166-
--scopes "/subscriptions/\${SUBSCRIPTION_ID}/resourceGroups/\${RESOURCE_GROUP}")
167-
168-
# Extract credentials from JSON output
169-
APP_ID=$(echo "$SP_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['appId'])")
170-
PASSWORD=$(echo "$SP_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['password'])")
171-
TENANT_ID=$(echo "$SP_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['tenant'])")
172-
173-
echo "APP_ID=$APP_ID TENANT_ID=$TENANT_ID"
174-
echo "⚠️ PASSWORD is shown once only. Store it securely (used in next step)."
175+
--scope "/subscriptions/\${SUBSCRIPTION_ID}/resourceGroups/\${RESOURCE_GROUP}"
175176
\`\`\`
176177
177-
> **Note:** The password (client secret) is displayed only once. You'll use it in the next step to create service connections, then it will be securely stored in Azure DevOps.
178+
> **Note:** User-assigned managed identities have no passwords or secrets. The RBAC role is assigned using the managed identity's principal ID, not a client ID.
178179
179180
---
180181
@@ -195,25 +196,70 @@ SUBSCRIPTION_NAME=$(az account show --subscription "\${SUBSCRIPTION_ID}" --query
195196
196197
## Step 4 — Create Service Connections
197198
198-
> ⚠️ **Security Note:** The service principal password is set via environment variable and cleared immediately after creating service connections.
199+
> ⚠️ **Note:** Workload identity federation means Azure DevOps exchanges its own OIDC token for an Azure token at runtime — no stored secrets. Creating a WIF service connection is a two-step process: create the connection (which generates an issuer/subject), then create a federated credential on the managed identity.
200+
201+
The function below handles both steps. Call it once for each service connection:
199202
200203
\`\`\`bash
201-
# Set the service principal password as environment variable
202-
export AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY="$PASSWORD"
204+
# Helper function: create a WIF service connection and link it to the managed identity
205+
create_wif_service_connection() {
206+
local SC_NAME="$1"
207+
208+
# Step A: Create the service connection (returns issuer + subject for federation)
209+
ENDPOINT_JSON=$(az devops invoke \\
210+
--area serviceEndpoint \\
211+
--resource endpoints \\
212+
--route-parameters project="\${AZDO_PROJECT}" \\
213+
--http-method POST \\
214+
--api-version "7.1" \\
215+
--in-file - << ENDJSON
216+
{
217+
"name": "\${SC_NAME}",
218+
"type": "azurerm",
219+
"url": "https://management.azure.com/",
220+
"authorization": {
221+
"scheme": "WorkloadIdentityFederation",
222+
"parameters": {
223+
"servicePrincipalId": "\${MI_CLIENT_ID}",
224+
"tenantid": "\${TENANT_ID}"
225+
}
226+
},
227+
"data": {
228+
"subscriptionId": "\${SUBSCRIPTION_ID}",
229+
"subscriptionName": "\${SUBSCRIPTION_NAME}",
230+
"environment": "AzureCloud",
231+
"scopeLevel": "Subscription",
232+
"creationMode": "Manual"
233+
}
234+
}
235+
ENDJSON
236+
)
237+
238+
ENDPOINT_ID=$(echo "\${ENDPOINT_JSON}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
239+
ISSUER=$(echo "\${ENDPOINT_JSON}" | python3 -c "import sys,json; print(json.load(sys.stdin)['authorization']['parameters']['workloadIdentityFederationIssuer'])")
240+
SUBJECT=$(echo "\${ENDPOINT_JSON}" | python3 -c "import sys,json; print(json.load(sys.stdin)['authorization']['parameters']['workloadIdentityFederationSubject'])")
241+
242+
# Step B: Create federated credential on the managed identity
243+
az identity federated-credential create \\
244+
--name "azdo-$(echo "\${SC_NAME}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')" \\
245+
--identity-name "\${MI_NAME}" \\
246+
--resource-group "\${MI_RESOURCE_GROUP}" \\
247+
--issuer "\${ISSUER}" \\
248+
--subject "\${SUBJECT}" \\
249+
--audiences "api://AzureADTokenExchange"
250+
251+
echo "✅ Service connection '\${SC_NAME}' created (ID: \${ENDPOINT_ID})"
252+
}
253+
254+
# Get subscription name (needed for service connection metadata)
255+
SUBSCRIPTION_NAME=$(az account show --subscription "\${SUBSCRIPTION_ID}" --query name -o tsv)
203256
204257
# Create base service connection
205-
az devops service-endpoint azurerm create \\
206-
--name "AZURE_SERVICE_CONNECTION" \\
207-
--azure-rm-service-principal-id "$APP_ID" \\
208-
--azure-rm-subscription-id "$SUBSCRIPTION_ID" \\
209-
--azure-rm-subscription-name "$SUBSCRIPTION_NAME" \\
210-
--azure-rm-tenant-id "$TENANT_ID"
258+
create_wif_service_connection "AZURE_SERVICE_CONNECTION"
211259
260+
# Create per-environment service connections
212261
${envServiceConnections}
213262
214-
# Clear the password from environment
215-
unset AZURE_DEVOPS_EXT_AZURE_RM_SERVICE_PRINCIPAL_KEY
216-
217263
# Verify service connections were created
218264
az devops service-endpoint list --query "[].name" -o table
219265
\`\`\`

0 commit comments

Comments
 (0)