Skip to content

Commit

Permalink
[Identity] Managed Identity test automation: Azure Functions and Weba…
Browse files Browse the repository at this point in the history
…pps (Azure#28554)
  • Loading branch information
KarishmaGhiya authored Mar 14, 2024
1 parent 4c0a0fa commit f9892bb
Show file tree
Hide file tree
Showing 24 changed files with 958 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,7 @@ sdk/template/template-dpg/src/src

# tshy
.tshy-build-tmp

# sshkey
sdk/**/sshkey
sdk/**/sshkey.pub
1 change: 1 addition & 0 deletions eng/.docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions sdk/identity/identity/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
src/**/*.js
integration/AzureFunctions/app.zip
integration/AzureWebApps/.azure/
!assets/fake-cert.pem
!assets/fake-cert-password.pem
Original file line number Diff line number Diff line change
@@ -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"
}
15 changes: 15 additions & 0 deletions sdk/identity/identity/integration/AzureFunctions/RunTest/host.json
Original file line number Diff line number Diff line change
@@ -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)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node"
},
"ConnectionStrings": {}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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();
}

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

20 changes: 20 additions & 0 deletions sdk/identity/identity/integration/AzureKubernetes/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
22 changes: 22 additions & 0 deletions sdk/identity/identity/integration/AzureKubernetes/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
48 changes: 48 additions & 0 deletions sdk/identity/identity/integration/AzureKubernetes/src/index.ts
Original file line number Diff line number Diff line change
@@ -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);
});
13 changes: 13 additions & 0 deletions sdk/identity/identity/integration/AzureKubernetes/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"alwaysStrict": true,
"outDir": "dist",
"rootDir": "."
}
}
27 changes: 27 additions & 0 deletions sdk/identity/identity/integration/AzureWebApps/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
60 changes: 60 additions & 0 deletions sdk/identity/identity/integration/AzureWebApps/src/index.ts
Original file line number Diff line number Diff line change
@@ -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`);
});
13 changes: 13 additions & 0 deletions sdk/identity/identity/integration/AzureWebApps/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"alwaysStrict": true,
"outDir": "dist",
},
"include": ["src"]
}
2 changes: 1 addition & 1 deletion sdk/identity/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 0 additions & 1 deletion sdk/identity/identity/test-resources.bicep

This file was deleted.

Loading

0 comments on commit f9892bb

Please sign in to comment.