[Identity] Managed Identity test automation: Azure Functions and Webapps (#28554)

This commit is contained in:
KarishmaGhiya 2024-03-13 19:44:52 -07:00 коммит произвёл GitHub
Родитель 4c0a0facf9
Коммит f9892bb7d4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
24 изменённых файлов: 958 добавлений и 3 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -186,3 +186,7 @@ sdk/template/template-dpg/src/src
# tshy
.tshy-build-tmp
# sshkey
sdk/**/sshkey
sdk/**/sshkey.pub

Просмотреть файл

@ -23,6 +23,7 @@ omitted_paths:
- sdk/storage/storage-datalake/README.md
- sdk/storage/storage-internal-avro/*
- sdk/test-utils/*/README.md
- sdk/identity/identity/integration/*
language: js
root_check_enabled: True

2
sdk/identity/identity/.gitignore поставляемый
Просмотреть файл

@ -1,3 +1,5 @@
src/**/*.js
integration/AzureFunctions/app.zip
integration/AzureWebApps/.azure/
!assets/fake-cert.pem
!assets/fake-cert-password.pem

Просмотреть файл

@ -0,0 +1,17 @@
{
"bindings": [
{
"type": "httpTrigger",
"direction": "in",
"authLevel": "anonymous",
"methods": ["get"],
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "./dist/authenticateToStorageFunction.js"
}

Просмотреть файл

@ -0,0 +1,15 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.0.0, 5.0.0)"
}
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node"
},
"ConnectionStrings": {}
}

Просмотреть файл

@ -0,0 +1,29 @@
{
"name": "@azure-samples/azure-function-test",
"version": "1.0.0",
"description": "",
"main": "dist/authenticateToStorageFunction.js",
"scripts": {
"build": "tsc",
"build:production": "npm run prestart && npm prune --production",
"clean": "rimraf --glob dist dist-*",
"prestart": "npm run build:production && func extensions install",
"start:host": "func start --typescript",
"start": "npm-run-all --parallel start:host watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.17.0",
"@azure/functions": "^4.1.0",
"applicationinsights": "^2.9.2",
"tslib": "^1.10.0"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"typescript": "^5.3.3",
"rimraf": "^5.0.5"
}
}

Просмотреть файл

@ -0,0 +1,61 @@
import { BlobServiceClient } from "@azure/storage-blob";
import { ManagedIdentityCredential } from "@azure/identity";
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
export async function authenticateStorage(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
try {
context.log('Http function was triggered.');
//parse the request body
await authToStorageHelper(context);
return {
// status: 200, /* Defaults to 200 */
body: "Successfully authenticated with storage",
};
} catch (error: any) {
context.log(error);
return {
status: 400,
body: error,
};
}
};
app.http('authenticateStorage', {
methods: ['GET', 'POST'],
authLevel: "anonymous",
handler: authenticateStorage
});
async function authToStorageHelper(context: InvocationContext): Promise<void> {
// This will use the system managed identity
const credential1 = new ManagedIdentityCredential();
const clientId = process.env.IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID!;
const account1 = process.env.IDENTITY_STORAGE_NAME_1;
const account2 = process.env.IDENTITY_STORAGE_NAME_2;
const credential2 = new ManagedIdentityCredential({ "clientId": clientId });
const client1 = new BlobServiceClient(`https://${account1}.blob.core.windows.net`, credential1);
const client2 = new BlobServiceClient(`https://${account2}.blob.core.windows.net`, credential2);
context.log("Getting containers for storage account client: system managed identity")
let iter = client1.listContainers();
let i = 1;
context.log("Client with system assigned identity");
let containerItem = await iter.next();
while (!containerItem.done) {
context.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
context.log("Getting properties for storage account client: user assigned managed identity")
iter = client2.listContainers();
context.log("Client with user assigned identity");
containerItem = await iter.next();
while (!containerItem.done) {
context.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"alwaysStrict": true,
"outDir": "dist",
},
"include": ["src"]
}

Просмотреть файл

@ -0,0 +1,20 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
ARG NODE_VERSION=20
# docker can't tell when the repo has changed and will therefore cache this layer
# internal users should provide MCR registry to build via 'docker build . --build-arg REGISTRY="mcr.microsoft.com/mirror/docker/library/"'
# public OSS users should simply leave this argument blank or ignore its presence entirely
ARG REGISTRY=""
FROM ${REGISTRY}node:${NODE_VERSION}-alpine as repo
RUN apk --no-cache add git
RUN git clone https://github.com/azure/azure-sdk-for-js --single-branch --branch main --depth 1 /azure-sdk-for-js
WORKDIR /azure-sdk-for-js/sdk/identity/identity/test/integration/AzureKubernetes
RUN npm install
RUN npm install -g typescript
RUN tsc -p .
CMD ["node", "index"]

Просмотреть файл

@ -0,0 +1,22 @@
{
"name": "@azure-samples/azure-kubernetes-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"start": "ts-node src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.17.0",
"tslib": "^1.10.0",
"ts-node": "10.9.2"
},
"devDependencies": {
"typescript": "^5.3.3"
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { ManagedIdentityCredential } from "@azure/identity";
import { BlobServiceClient } from "@azure/storage-blob";
import * as dotenv from "dotenv";
// Initialize the environment
dotenv.config();
async function main(): Promise<void> {
let systemSuccessMessage = "";
try{
const account1 = process.env.IDENTITY_STORAGE_NAME_1;
const account2 = process.env.IDENTITY_STORAGE_NAME_2;
const credentialSystemAssigned = new ManagedIdentityCredential();
const credentialUserAssigned = new ManagedIdentityCredential({clientId: process.env.IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID})
const client1 = new BlobServiceClient(`https://${account1}.blob.core.windows.net`, credentialSystemAssigned);
const client2 = new BlobServiceClient(`https://${account2}.blob.core.windows.net`, credentialUserAssigned);
let iter = client1.listContainers();
let i = 1;
console.log("Client with system assigned identity");
let containerItem = await iter.next();
while (!containerItem.done) {
console.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
systemSuccessMessage = "Successfully acquired token with system-assigned ManagedIdentityCredential"
console.log("Client with user assigned identity")
iter = client2.listContainers();
i = 1;
containerItem = await iter.next();
while (!containerItem.done) {
console.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
console.log("Successfully acquired tokens with async ManagedIdentityCredential")
}
catch(e){
console.error(`${e} \n ${systemSuccessMessage}`);
}
}
main().catch((err) => {
console.log("error code: ", err.code);
console.log("error message: ", err.message);
console.log("error stack: ", err.stack);
});

Просмотреть файл

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"alwaysStrict": true,
"outDir": "dist",
"rootDir": "."
}
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"name": "@azure-samples/azure-web-apps-test",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"clean": "rimraf --glob dist dist-*",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.17.0",
"express": "^4.18.2",
"tslib": "^1.10.0"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"typescript": "^5.3.3",
"@types/express": "^4.17.21",
"dotenv": "16.4.4",
"rimraf": "^5.0.5"
}
}

Просмотреть файл

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import express from "express";
import { ManagedIdentityCredential } from "@azure/identity";
import { BlobServiceClient } from "@azure/storage-blob";
import dotenv from "dotenv";
// Initialize the environment
dotenv.config();
const app = express();
app.get("/", (req: express.Request, res: express.Response) => {
res.send("Ok")
})
app.get("/sync", async (req: express.Request, res: express.Response) => {
let systemSuccessMessage = "";
try {
const account1 = process.env.IDENTITY_STORAGE_NAME_1;
const credentialSystemAssigned = new ManagedIdentityCredential();
const client1 = new BlobServiceClient(`https://${account1}.blob.core.windows.net`, credentialSystemAssigned);
let iter = client1.listContainers();
let i = 0;
console.log("Client with system assigned identity");
let containerItem = await iter.next();
while (!containerItem.done) {
console.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
console.log("Client with system assigned identity");
console.log("Properties of the 1st client =", iter);
systemSuccessMessage = "Successfully acquired token with system-assigned ManagedIdentityCredential"
console.log(systemSuccessMessage);
}
catch (e) {
console.error(e);
}
try {
const account2 = process.env.IDENTITY_STORAGE_NAME_2;
const credentialUserAssigned = new ManagedIdentityCredential({ clientId: process.env.IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID })
const client2 = new BlobServiceClient(`https://${account2}.blob.core.windows.net`, credentialUserAssigned);
let iter = client2.listContainers();
let i = 0;
console.log("Client with user assigned identity")
let containerItem = await iter.next();
while (!containerItem.done) {
console.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter.next();
}
res.status(200).send("Successfully acquired tokens with async ManagedIdentityCredential")
}
catch (e) {
console.error(e);
res.status(500).send(`${e} \n ${systemSuccessMessage}`);
}
})
app.listen(8080, () => {
console.log(`Authorization code redirect server listening on port 8080`);
});

Просмотреть файл

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"alwaysStrict": true,
"outDir": "dist",
},
"include": ["src"]
}

Просмотреть файл

@ -55,7 +55,7 @@
"format": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
"check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
"integration-test:browser": "echo skipped",
"integration-test:node": "dev-tool run test:node-js-input -- --timeout 180000 'dist-esm/test/public/node/*.spec.js' 'dist-esm/test/internal/node/*.spec.js'",
"integration-test:node": "dev-tool run test:node-ts-input -- --timeout 180000 'test/public/node/*.spec.ts' 'test/internal/node/*.spec.ts' 'test/integration/*.spec.ts'",
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
"lint:fix": "eslint package.json api-extractor.json src test --ext .ts --fix --fix-type [problem,suggestion]",
"lint": "eslint package.json api-extractor.json src test --ext .ts",

Просмотреть файл

@ -1 +0,0 @@
param baseName string

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { ServiceClient } from "@azure/core-client";
import { createPipelineRequest } from "@azure/core-rest-pipeline";
import { assert } from "chai";
import { Context } from "mocha";
import { isLiveMode } from "@azure-tools/test-recorder";
describe("AzureFunctions Integration test", function () {
it("test the Azure Functions endpoint where the sync MI credential is used.", async function (this: Context) {
if (!isLiveMode()) {
this.skip();
}
const baseUri = baseUrl();
const client = new ServiceClient({ baseUri: baseUri });
const pipelineRequest = createPipelineRequest({
url: baseUri,
method: "GET",
});
const response = await client.sendRequest(pipelineRequest);
console.log(response.bodyAsText);
assert.equal(response.status, 200, `Expected status 200. Received ${response.status}`);
assert.equal(
response.bodyAsText,
"Successfully authenticated with storage",
`Expected message: "Successfully authenticated with storage". Received message: ${response.bodyAsText}`,
);
});
});
function baseUrl(): string {
const functionName = process.env.IDENTITY_FUNCTION_NAME;
if (!functionName) {
console.log("IDENTITY_FUNCTION_NAME is not set");
throw new Error("IDENTITY_FUNCTION_NAME is not set");
}
return `https://${functionName}.azurewebsites.net/api/authenticateStorage`;
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { ServiceClient } from "@azure/core-client";
import { createPipelineRequest } from "@azure/core-rest-pipeline";
import { assert } from "chai";
import { Context } from "mocha";
import { isLiveMode } from "@azure-tools/test-recorder";
describe("AzureWebApps Integration test", function () {
it("test the Azure Web Apps endpoint where the MI credential is used.", async function (this: Context) {
if (!isLiveMode()) {
this.skip();
}
const baseUri = baseUrl();
const client = new ServiceClient({ baseUri: baseUri });
const pipelineRequest = createPipelineRequest({
url: baseUri,
method: "GET",
});
const response = await client.sendRequest(pipelineRequest);
console.log(response.bodyAsText);
assert.equal(response.status, 200, `Expected status 200. Received ${response.status}`);
});
});
function baseUrl(): string {
const webAppName = process.env.IDENTITY_WEBAPP_NAME;
if (!webAppName) {
console.log("IDENTITY_WEBAPP_NAME is not set");
throw new Error("IDENTITY_WEBAPP_NAME is not set");
}
return `https://${webAppName}.azurewebsites.net/sync`;
}

Просмотреть файл

@ -10,5 +10,5 @@
}
},
"include": ["src/**/*", "test/**/*", "samples-dev/**/*.ts"],
"exclude": ["test/manual*/**/*", "node_modules"]
"exclude": ["test/manual*/**/*", "integration/**", "node_modules"]
}

Просмотреть файл

@ -0,0 +1,139 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/New-TestResources.ps1 from the repository root.
param (
[Parameter(ValueFromRemainingArguments = $true)]
$RemainingArguments,
[Parameter()]
[hashtable] $DeploymentOutputs
)
# If not Linux, skip this script.
# if ($isLinux -ne "Linux") {
# Write-Host "Skipping post-deployment because not running on Linux."
# return
# }
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $true
$webappRoot = "$PSScriptRoot/identity/integration" | Resolve-Path
$workingFolder = $webappRoot;
Write-Host "Working directory: $workingFolder"
az login --service-principal -u $DeploymentOutputs['IDENTITY_CLIENT_ID'] -p $DeploymentOutputs['IDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['IDENTITY_TENANT_ID']
az account set --subscription $DeploymentOutputs['IDENTITY_SUBSCRIPTION_ID']
# Azure Functions app deployment
Write-Host "Building the code for functions app"
Push-Location "$webappRoot/AzureFunctions/RunTest"
npm install
npm run build
Pop-Location
Write-Host "starting azure functions deployment"
Compress-Archive -Path "$workingFolder/AzureFunctions/RunTest/*" -DestinationPath "$workingFolder/AzureFunctions/app.zip" -Force
az functionapp deployment source config-zip -g $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] -n $DeploymentOutputs['IDENTITY_FUNCTION_NAME'] --src "$workingFolder/AzureFunctions/app.zip"
Remove-Item -Force "$workingFolder/AzureFunctions/app.zip"
Write-Host "Deployed function app"
# $image = "$loginServer/identity-functions-test-image"
# docker build --no-cache -t $image "$workingFolder/AzureFunctions"
# docker push $image
# az functionapp config container set -g $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] -n $DeploymentOutputs['IDENTITY_FUNCTION_NAME'] -i $image -r $loginServer -p $(az acr credential show -n $DeploymentOutputs['IDENTITY_ACR_NAME'] --query "passwords[0].value" -o tsv) -u $(az acr credential show -n $DeploymentOutputs['IDENTITY_ACR_NAME'] --query username -o tsv)
# Azure Web Apps app deployment
# Push-Location "$webappRoot/AzureWebApps"
# npm install
# npm run build
# Compress-Archive -Path "$workingFolder/AzureWebApps/*" -DestinationPath "$workingFolder/AzureWebApps/app.zip" -Force
# az webapp deploy --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --name $DeploymentOutputs['IDENTITY_WEBAPP_NAME'] --src-path "$workingFolder/AzureWebApps/app.zip" --async true
# Remove-Item -Force "$workingFolder/AzureWebApps/app.zip"
# Pop-Location
Push-Location "$webappRoot/AzureWebApps"
npm install
npm run build
az webapp up --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --name $DeploymentOutputs['IDENTITY_WEBAPP_NAME'] --plan $DeploymentOutputs['IDENTITY_WEBAPP_PLAN'] --runtime NODE:18-lts
Pop-Location
Write-Host "Deployed webapp"
Write-Host "Sleeping for a bit to ensure logs is ready."
Start-Sleep -Seconds 300
# Write-Host "Sleeping for a bit to ensure container registry is ready."
# Start-Sleep -Seconds 20
# Write-Host "trying to login to acr"
# az acr login -n $DeploymentOutputs['IDENTITY_ACR_NAME']
# $loginServer = az acr show -n $DeploymentOutputs['IDENTITY_ACR_NAME'] --query loginServer -o tsv
# # Azure Kubernetes Service deployment
# $image = "$loginServer/identity-aks-test-image"
# docker build --no-cache -t $image "$workingFolder/AzureKubernetes"
# docker push $image
# Attach the ACR to the AKS cluster
# Write-Host "Attaching ACR to AKS cluster"
# az aks update -n $DeploymentOutputs['IDENTITY_AKS_CLUSTER_NAME'] -g $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --attach-acr $DeploymentOutputs['IDENTITY_ACR_NAME']
# $MIClientId = $DeploymentOutputs['IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID']
# $MIName = $DeploymentOutputs['IDENTITY_USER_DEFINED_IDENTITY_NAME']
# $SaAccountName = 'workload-identity-sa'
# $PodName = $DeploymentOutputs['IDENTITY_AKS_POD_NAME']
# $storageName = $DeploymentOutputs['IDENTITY_STORAGE_NAME_2']
# # Get the aks cluster credentials
# Write-Host "Getting AKS credentials"
# az aks get-credentials --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --name $DeploymentOutputs['IDENTITY_AKS_CLUSTER_NAME']
# #Get the aks cluster OIDC issuer
# Write-Host "Getting AKS OIDC issuer"
# $AKS_OIDC_ISSUER = az aks show -n $DeploymentOutputs['IDENTITY_AKS_CLUSTER_NAME'] -g $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --query "oidcIssuerProfile.issuerUrl" -otsv
# # Create the federated identity
# Write-Host "Creating federated identity"
# az identity federated-credential create --name $MIName --identity-name $MIName --resource-group $DeploymentOutputs['IDENTITY_RESOURCE_GROUP'] --issuer $AKS_OIDC_ISSUER --subject system:serviceaccount:default:workload-identity-sa
# # Build the kubernetes deployment yaml
# $kubeConfig = @"
# apiVersion: v1
# kind: ServiceAccount
# metadata:
# annotations:
# azure.workload.identity/client-id: $MIClientId
# name: $SaAccountName
# namespace: default
# ---
# apiVersion: v1
# kind: Pod
# metadata:
# name: $PodName
# namespace: default
# labels:
# azure.workload.identity/use: "true"
# spec:
# serviceAccountName: $SaAccountName
# containers:
# - name: $PodName
# image: $image
# env:
# - name: IDENTITY_STORAGE_NAME
# value: "$StorageName"
# ports:
# - containerPort: 80
# nodeSelector:
# kubernetes.io/os: linux
# "@
# Set-Content -Path "$workingFolder/kubeconfig.yaml" -Value $kubeConfig
# Write-Host "Created kubeconfig.yaml with contents:"
# Write-Host $kubeConfig
# # Apply the config
# kubectl apply -f "$workingFolder/kubeconfig.yaml" --overwrite=true
# Write-Host "Applied kubeconfig.yaml"
# az logout

Просмотреть файл

@ -0,0 +1,59 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/New-TestResources.ps1 from the repository root.
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param (
# Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors).
[Parameter(ValueFromRemainingArguments = $true)]
$RemainingArguments,
[Parameter()]
[string] $Location = '',
[Parameter()]
[ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')]
[string] $TestApplicationId,
[Parameter()]
[string] $TestApplicationSecret,
[Parameter()]
[ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')]
[string] $SubscriptionId,
[Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $TenantId,
[Parameter()]
[switch] $CI = ($null -ne $env:SYSTEM_TEAMPROJECTID)
)
Import-Module -Name $PSScriptRoot/../../eng/common/scripts/X509Certificate2 -Verbose
ssh-keygen -t rsa -b 4096 -f $PSScriptRoot/sshKey -N '' -C ''
$sshKey = Get-Content $PSScriptRoot/sshKey.pub
$templateFileParameters['sshPubKey'] = $sshKey
Write-Host "Sleeping for a bit to ensure service principal is ready."
Start-Sleep -s 45
if ($CI) {
# Install this specific version of the Azure CLI to avoid https://github.com/Azure/azure-cli/issues/28358.
pip install azure-cli=="2.56.0"
}
$az_version = az version
Write-Host "Azure CLI version: $az_version"
az login --service-principal -u $TestApplicationId -p $TestApplicationSecret --tenant $TenantId
az account set --subscription $SubscriptionId
$versions = az aks get-versions -l westus -o json | ConvertFrom-Json
Write-Host "AKS versions: $($versions | ConvertTo-Json -Depth 100)"
$patchVersions = $versions.values | Where-Object { $_.isPreview -eq $null } | Select-Object -ExpandProperty patchVersions
Write-Host "AKS patch versions: $($patchVersions | ConvertTo-Json -Depth 100)"
$latestAksVersion = $patchVersions | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Sort-Object -Descending | Select-Object -First 1
Write-Host "Latest AKS version: $latestAksVersion"
$templateFileParameters['latestAksVersion'] = $latestAksVersion

Просмотреть файл

@ -0,0 +1,332 @@
@minLength(6)
@maxLength(23)
@description('The base resource name.')
param baseName string = resourceGroup().name
@description('The location of the resource. By default, this is the same as the resource group.')
param location string = resourceGroup().location
@description('The client OID to grant access to test resources.')
param testApplicationOid string
@minLength(5)
@maxLength(50)
@description('Provide a globally unique name of the Azure Container Registry')
param acrName string = 'acr${uniqueString(resourceGroup().id)}'
@description('The latest AKS version available in the region.')
param latestAksVersion string
@description('The SSH public key to use for the Linux VMs.')
param sshPubKey string
@description('The admin user name for the Linux VMs.')
param adminUserName string = 'azureuser'
// https://learn.microsoft.com/azure/role-based-access-control/built-in-roles
// var blobContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor
var blobOwner = subscriptionResourceId('Microsoft.Authorization/roleDefinitions','b7e6dc6d-f1e8-4753-8033-0f276bb0955b') // Storage Blob Data Owner
var websiteContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') // Website Contributor
// Cluster parameters
var kubernetesVersion = latestAksVersion
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: baseName
location: location
}
resource blobRoleWeb 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: storageAccount
name: guid(resourceGroup().id, blobOwner)
properties: {
principalId: web.identity.principalId
roleDefinitionId: blobOwner
principalType: 'ServicePrincipal'
}
}
resource blobRoleFunc 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: storageAccount
name: guid(resourceGroup().id, blobOwner, 'azureFunction')
properties: {
principalId: azureFunction.identity.principalId
roleDefinitionId: blobOwner
principalType: 'ServicePrincipal'
}
}
resource blobRoleCluster 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: storageAccount
name: guid(resourceGroup().id, blobOwner, 'kubernetes')
properties: {
principalId: kubernetesCluster.identity.principalId
roleDefinitionId: blobOwner
principalType: 'ServicePrincipal'
}
}
resource blobRole2 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: storageAccount2
name: guid(resourceGroup().id, blobOwner, userAssignedIdentity.id)
properties: {
principalId: userAssignedIdentity.properties.principalId
roleDefinitionId: blobOwner
principalType: 'ServicePrincipal'
}
}
resource webRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: web
name: guid(resourceGroup().id, websiteContributor, 'web')
properties: {
principalId: testApplicationOid
roleDefinitionId: websiteContributor
principalType: 'ServicePrincipal'
}
}
resource webRole2 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: azureFunction
name: guid(resourceGroup().id, websiteContributor, 'azureFunction')
properties: {
principalId: testApplicationOid
roleDefinitionId: websiteContributor
principalType: 'ServicePrincipal'
}
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
name: baseName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}
resource storageAccount2 'Microsoft.Storage/storageAccounts@2021-08-01' = {
name: '${baseName}2'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}
resource farm 'Microsoft.Web/serverfarms@2021-03-01' = {
name: '${baseName}_farm'
location: location
sku: {
name: 'B1'
tier: 'Basic'
size: 'B1'
family: 'B'
capacity: 1
}
properties: {
reserved: true
}
kind: 'app,linux'
}
resource web 'Microsoft.Web/sites@2022-09-01' = {
name: '${baseName}webapp'
location: location
kind: 'app'
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}' : { }
}
}
properties: {
enabled: true
serverFarmId: farm.id
httpsOnly: true
keyVaultReferenceIdentity: 'SystemAssigned'
siteConfig: {
linuxFxVersion: 'NODE|18-lts'
http20Enabled: true
minTlsVersion: '1.2'
appSettings: [
{
name: 'AZURE_REGIONAL_AUTHORITY_NAME'
value: 'eastus'
}
{
name: 'IDENTITY_STORAGE_NAME_1'
value: storageAccount.name
}
{
name: 'IDENTITY_STORAGE_NAME_2'
value: storageAccount2.name
}
{
name: 'IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID'
value: userAssignedIdentity.properties.clientId
}
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'true'
}
]
}
}
}
resource azureFunction 'Microsoft.Web/sites@2022-09-01' = {
name: '${baseName}func'
location: location
kind: 'functionapp'
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}' : { }
}
}
properties: {
enabled: true
serverFarmId: farm.id
httpsOnly: true
keyVaultReferenceIdentity: 'SystemAssigned'
siteConfig: {
alwaysOn: true
http20Enabled: true
minTlsVersion: '1.2'
appSettings: [
{
name: 'IDENTITY_STORAGE_NAME_1'
value: storageAccount.name
}
{
name: 'IDENTITY_STORAGE_NAME_2'
value: storageAccount2.name
}
{
name: 'IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID'
value: userAssignedIdentity.properties.clientId
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: toLower('${baseName}-func')
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'node'
}
{
name: 'DOCKER_CUSTOM_IMAGE_NAME'
value: 'mcr.microsoft.com/azure-functions/node:4-node18-appservice-stage3'
}
]
}
}
}
resource publishPolicyWeb 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01' = {
kind: 'app'
parent: web
name: 'scm'
properties: {
allow: true
}
}
resource publishPolicyFunction 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01' = {
kind: 'functionapp'
parent: azureFunction
name: 'scm'
properties: {
allow: true
}
}
resource acrResource 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = {
name: acrName
location: location
sku: {
name: 'Basic'
}
properties: {
adminUserEnabled: true
}
}
resource kubernetesCluster 'Microsoft.ContainerService/managedClusters@2023-06-01' = {
name: baseName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
kubernetesVersion: kubernetesVersion
enableRBAC: true
dnsPrefix: 'identitytest'
agentPoolProfiles: [
{
name: 'agentpool'
count: 1
vmSize: 'Standard_D2s_v3'
osDiskSizeGB: 128
osDiskType: 'Managed'
kubeletDiskType: 'OS'
type: 'VirtualMachineScaleSets'
enableAutoScaling: false
orchestratorVersion: kubernetesVersion
mode: 'System'
osType: 'Linux'
osSKU: 'Ubuntu'
}
]
linuxProfile: {
adminUsername: adminUserName
ssh: {
publicKeys: [
{
keyData: sshPubKey
}
]
}
}
oidcIssuerProfile: {
enabled: true
}
securityProfile: {
workloadIdentity: {
enabled: true
}
}
}
}
output IDENTITY_WEBAPP_NAME string = web.name
output IDENTITY_WEBAPP_PLAN string = farm.name
output IDENTITY_USER_DEFINED_IDENTITY string = userAssignedIdentity.id
output IDENTITY_USER_DEFINED_IDENTITY_CLIENT_ID string = userAssignedIdentity.properties.clientId
output IDENTITY_USER_DEFINED_IDENTITY_NAME string = userAssignedIdentity.name
output IDENTITY_STORAGE_NAME_1 string = storageAccount.name
output IDENTITY_STORAGE_NAME_2 string = storageAccount2.name
output IDENTITY_FUNCTION_NAME string = azureFunction.name
output IDENTITY_AKS_CLUSTER_NAME string = kubernetesCluster.name
output IDENTITY_AKS_POD_NAME string = 'javascript-test-app'
output IDENTITY_ACR_NAME string = acrResource.name
output IDENTITY_ACR_LOGIN_SERVER string = acrResource.properties.loginServer