[Identity] Managed Identity test automation: Azure Functions and Webapps (#28554)
This commit is contained in:
Родитель
4c0a0facf9
Коммит
f9892bb7d4
|
@ -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
|
||||
|
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче