Azure node SDK to get more than 50 virtual machines - node.js

I' using the Azure node SDK to get all virtual machines for the subscription :
var computeClient = new computeManagementClient.ComputeManagementClient(credentials, subscriptionId);
var clientNetworkManagement = new NetworkManagementClient(credentials, subscriptionId);
computeClient.virtualMachines.listAll(function (err, result) {
returnResult(result);
});
But I have subscription with more than 50 vm's and that call only return 50 vm's max.
It's possible to get more than 50 vms with this function computeClient.virtualMachines.listAll ?
https://github.com/Azure-Samples/compute-node-manage-vm
Thx

I tried to reproduce your issue, but failed that I can list all VMs via my code as below. Before to run my code, I assigned a role Virtual Machine Contributor(or you can use higher level role like Contributer or Owner) to my app registed in AzureAD for my current subscription, you can refer to the offical document Manage access to Azure resources using RBAC and the Azure portal to know it.
var msRestAzure = require('ms-rest-azure');
var ComputeManagementClient = require('azure-arm-compute');
var clientId = process.env['CLIENT_ID'] || '<your client id>';
var domain = process.env['DOMAIN'] || '<your tenant id>';
var secret = process.env['APPLICATION_SECRET'] || '<your client secret>';
var subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'] || '<your subscription id for listing all VMs in it>';
var computeClient;
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain, function (err, credentials, subscriptions) {
computeClient = new ComputeManagementClient(credentials, subscriptionId);
computeClient.virtualMachines.listAll(function (err, result) {
console.log(result.length);
});
});
On Azure portal, there are 155 VMs list in my current subscription as the figure below. However, the result of my code only is 153 VMs. I don't know why the results are different, but my code result is same with Azure CLI command az vm list | grep vmId | wc -l.
Fig 1. The number of VMs in my current subscription
Fig 2. The result of my code
Fig 3. The result of Azure CLI command az vm list|grep vmId|wc -l
Per my experience, I guess your issue was caused by assigning the lower permission role for your app to only list VMs that you have default accessing permission.
Any concern or update is very helpful for understanding what your real issue is, please feel free to let me know.

I don't know if this is the best way to solve the problem, but I find a solution:
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain, function (err, credentials, subscriptions) {
computeClient = new ComputeManagementClient(credentials, subscriptionId);
computeClient.virtualMachines.listAll(function (err, result, httpRequest, response) {
let myResult = JSON.parse(response.body);
console.log(result.length);
nextLink = myResult.nextLink;
console.log(nextLink);
computeClient.virtualMachines.listAllNext(nextLink, function (err, result, request, response) {
console.log(result.length);
});
});
});
The first call (listAll) return 50 Vm's and "nextLink" value.
Than I call listAllNext(nextLink,... that return the others 39 Vm's

Related

Get Azure VM related details using Microsoft.Azure.Management.Fluent

I am trying to use Microsoft.Azure.Management.Fluent and related set of packages,
I need following details about ALL azure VM in my subscription:
Who created VM, Region of VM, VmSize, Current Status of VM ( Like Stopped/ Running/ Deallocated etc),
I also need
History of VM in terms of duration this VM was up and running for last x months/ weeks.
Is this possible using Microsoft.Azure.Management.Fluent packages?
If you want to know the VM starting and stopping time, we can get it from Azure activity log. Regarding how to retrieve the activity log, we can use Microsoft.Azure.Management.Monitor.Fluent package.
For example
create a service principal and assign Azure RABC role to the sp(I use Azure CLI)
az login
#it will create a service principal and assign contributor role to the sp
az ad sp create-for-rbac -n "jonsp2"
Install package
// for more details about the package, please refer to https://www.nuget.org/packages/Microsoft.Azure.Management.Fluent/
Install-Package Microsoft.Azure.Management.Fluent -Version 1.34.0
Code
AzureCredentials credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(
clientId, // the sp appId
clientSecret, // the sp password
tenantId, // the sp tenant
AzureEnvironment.AzureGlobalCloud);
var azure = Microsoft.Azure.Management.Fluent.Azure.Configure()
.Authenticate(credentials)
.WithSubscription(subscriptionId);
var vms = await azure.VirtualMachines.ListAsync();
foreach (var vm in vms)
{
var staus = vm.PowerState.Value; // vm power state
var region = vm.RegionName; // vm region
var size = vm.Size.Value; // vm size
var logs = await azure.ActivityLogs.DefineQuery()
.StartingFrom(DateTime.Now.AddDays(-1))
.EndsBefore(DateTime.Now)
.WithAllPropertiesInResponse()
.FilterByResource("/subscriptions/e5b0fcfa-e859-43f3-8d84-5e5fe29f4c68/resourceGroups/jimtest/providers/Microsoft.Compute/virtualMachines/testvm")
.ExecuteAsync();
List<DateTime?> stopTime = new List<DateTime?>();
List<DateTime?> startTime = new List<DateTime?>();
foreach (var log in logs)
{
// get stop time
if ((log.OperationName.LocalizedValue == "Deallocate Virtual Machine") & (log.Status.LocalizedValue == "Succeeded"))
{
stopTime.Add(log.EventTimestamp);
}
// get start tim
if ((log.OperationName.LocalizedValue == "Strat Virtual Machine") & (log.Status.LocalizedValue == "Succeeded"))
{
startTime.Add(log.EventTimestamp);
}
}
}

Authorize a user for azure blob access

I am authenticating the users to my web page using the microsoft login via adal-node library.
adal-node has an AuthenticationContext from which we can get a JWT token using acquireTokenWithAuthorizationCode.
So, the users of my active directory app can now successfully login with their Microsoft accounts.
Now, the question is how to get their RBAC roles for a specific storageaccount/container/blob using the above received JWT Token? Is that even possible?
Or should I be using a library like azure-arm-authorization for this purpose? I have set the RBAC roles for each storageaccount/container/blob but I am not finding any online documentation on how to get these roles for every logged in user of my app.
Any help would be invaluable.
TL;DR How do I authorize azure blobs?
According to my research, if we want to use Azure AD Authentication to acess Azure blob storage, we need to assign Azure RABC role for Azure storage account or container. For more details, please refer to here. Besides, we can use Azure CLI to assign role and get role assignment.
For example
# assign role
az role assignment create \
--role "Storage Blob Data Contributor" \
--assignee <email> \
--scope "/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>"
# list role assignment of the resource
az role assignment list --scope "/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>"
For further information, please read the article
Update1
If you want to use nodejs sdk to get the role assignment, please refer to the following code
const authorizationManagement = require('azure-arm-authorization');
const msrestAzure = require('ms-rest-azure');
const scope = '/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>';
const subscriptionId = 'e5b0fcfa-e859-43f3-8d84-5e5fe29f4c68';
msrestAzure.interactiveLogin().then(credentials => {
const client = new authorizationManagement(credentials, subscriptionId);
client.roleAssignments.listForScope(scope).then(result => {
result.forEach(element => {
client.roleDefinitions.getById(element.roleDefinitionId).then(result => {
console.log("principal ID: "+ element.principalId+"\nrole name: "+result.roleName)
});
});
});
})
For more details, please refer to Get access control list (IAM) of a resource group in Node.js
Update2
According to my test, if you want to use azure-arm-authorization with adal-node. Please refer to the following code
const authorizationManagement = require('azure-arm-authorization');
const TokenCredentials = require('ms-rest').TokenCredentials
const adal = require('adal-node').AuthenticationContext;
const scope = '/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>';
const subscriptionId = 'e5b0fcfa-e859-43f3-8d84-5e5fe29f4c68';
// use service principal to get access token with adal-node
/*
If you do not have a service principal, you can use the following Azure CLI command(https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac) to create it
az ad sp create-for-rbac -n "MyApp" --role contributor
*/
const tenant = 'your-tenant-id';
const authorityUrl = "https://login.microsoftonline.com/" + tenant;
const clientId = 'your-client-id';
const clientSecret = 'your-client-secret';
const resource = 'https://management.azure.com/';
const context = new adal(authorityUrl);
context.acquireTokenWithClientCredentials(
resource,
clientId,
clientSecret,
(err, tokenResponse) => {
if (err) {
console.log(`Token generation failed due to ${err}`);
} else {
const credentials = new TokenCredentials(tokenResponse.accessToken);
const client = new authorizationManagement(credentials, subscriptionId);
client.roleAssignments.listForScope(scope).then(result => {
result.forEach(element => {
client.roleDefinitions.getById(element.roleDefinitionId).then(result => {
console.log("principal ID: " + element.principalId + "\nrole name: " + result.roleName)
});
});
});
}
}
);
Besides if you want to know how to use passport-azure-ad to get role assignment, you can use passport-azure-ad to get AD access token then call the Azure rest API. Regarding how to implement it, you can refer to the sample.

Using Azure Managed Service Identities to scale App Service Plan, but none listed

I am attempting to set up an Azure Function that will scale our App Service Plan to be larger during business hours, and smaller outside of them. I drew from the answer to this question.
public static class ScaleUpWeekdayMornings
{
[FunctionName(nameof(ScaleUpWeekdayMornings))]
public static async Task Run([TimerTrigger("0 0 13 * * 1-5")]TimerInfo myTimer, ILogger log) // Uses GMT timezone
{
var resourceGroup = "DesignServiceResourceGroup";
var appServicePlanName = "DesignService";
var webSiteManagementClient = await Utilities.GetWebSiteManagementClient();
var appServicePlanRequest = await webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup);
appServicePlanRequest.Body.ToList().ForEach(x => log.LogInformation($">>>{x.Name}"));
var appServicePlan = appServicePlanRequest.Body.Where(x => x.Name.Equals(appServicePlanName)).FirstOrDefault();
if (appServicePlan == null)
{
log.LogError("Could not find app service plan.");
return;
}
appServicePlan.Sku.Family = "P";
appServicePlan.Sku.Name = "P2V2";
appServicePlan.Sku.Size = "P2V2";
appServicePlan.Sku.Tier = "Premium";
appServicePlan.Sku.Capacity = 1;
var updateResult = await webSiteManagementClient.AppServicePlans.CreateOrUpdateWithHttpMessagesAsync(resourceGroup, appServicePlanName, appServicePlan);
}
}
public static class Utilities
{
public static async Task<WebSiteManagementClient> GetWebSiteManagementClient()
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com/");
var tokenCredentials = new TokenCredentials(accessToken);
return new WebSiteManagementClient(tokenCredentials)
{
SubscriptionId = "<realSubscriptionIdHere>",
};
}
}
When I run this locally, it works, and actually performs the scale up on our Azure App Service Plan (I believe via Azure CLI). However, when this Azure Function is deployed to Azure, it does not find any App Service Plans. It hits the appservicePlan == null block and returns. I have turned on the Managed Service Identity for the deployed Azure Function, and granted it contributor permissions in the App Service I want to scale for.
Am I missing something? Why does
await webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup);
not return anything when run as part of a published Azure Function?
The problem was the permission was set on the App Service (which was an app on the App Service Plan). It needed to be set on the Resource Group to be able to read the App Service Plans.

Access Azure Batch from an Azure Function

I'm trying to use a Service Principle to access a Batch pool from an Azure Function and running into authentication issues that I don't understand. The initial login with the Service Principle works fine, but then using the credentials to access the batch pool returns a 401.
Below is a condensed version of my code with comments at the key points
module.exports.dispatch = function (context) {
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};
How can the initial login with a service principle work no problem, but then the credentials it returns not be able to access the batch pool?
The actual error says to check the auth header on the request, which I can see and the Authorisation header isn't even present.
I've triple checked the Active Directory access control for the batch account the App ID and secret are the ones belonging to the owner of the batch account. Any ideas what to try next?
The credentials expected by the Azure Batch npm client aren't the Azure Active Directory credentials/token, but the keys for the batch account. You can list your keys using the Azure CLI with a command like the following:
az batch account keys list -g "<resource-group-name>" -n "<batch-account-name>"
sample here
Then you can create the credentials parameter with those keys:
var credentials = new batch.SharedKeyCredentials('your-account-name', 'your-account-key');
You could still involve a Service Principal here if you wanted to store your batch keys in something like Key Vault, but then your code would be:
Get Service Principal auth against key vault to fetch name and key
Use name and key to create credentials
You cannot use the same OAuth token returned from the Azure Resource Management endpoint with Batch. Assuming your service principal has the correct RBAC permissions, auth with the Azure Batch endpoint: https://batch.core.windows.net/ instead (assuming you are using Public Azure).
You do not need to get the shared key credentials for the Batch account, credentials via AAD should be used instead if you are using an AAD service principal.
I happened to run across this same issue and I didn't have the option of using SharedKeyCredentials so I wanted to share my solution in case anyone else finds it helpful.
As fpark mentions, we need to get an OAuth token to use with Batch instead of the default Azure Resource Management. Below is the original code posted by Mark with the minor modification needed to make it work with Batch:
module.exports.dispatch = function (context) {
let authOptions = {tokenAudience: 'batch'};
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', authOptions, function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};

Error:InvalidAuthenticationTokenTenant' The access token is from the wrong issuer

I am using Node js to authenticate into Azure AD to create a Data lake storage account, it logs in but for the account creation it gives the error: code: 'InvalidAuthenticationTokenTenant',
message: 'The access token is from the wrong issuer \'https://sts.windows.n
et\'. It must match the tenant \'https://sts.windows.net/\' associated with this subs
cription.
var msRestAzure = require('ms-rest-azure');
var adlsManagement = require("azure-arm-datalake-store");
msRestAzure.interactiveLogin(function(err, credentials) {
var accountName = 'testadlsacct';
var pathToEnumerate = '/myfolder';
var acccountClient = new adlsManagement.DataLakeStoreAccountClient(credentials, 'dxxxxxxx-dxxx-4xxx-bxxx-5xxxxxxxxx');
var filesystemClient = new adlsManagement.DataLakeStoreFileSystemClient(credentials);
var util = require('util');
var resourceGroupName = 'testrg';
var accountName = 'testadlsacct';
var location = 'eastus2';
var accountToCreate = {
tags: {
testtag1: 'testvalue1',
testtag2: 'testvalue2'
},
name: accountName,
location: location
};
var client= new adlsManagement.DataLakeStoreAccountClient(credentials, 'dxxxxxxxx-xxx-xxxx--xxxxxx');
client.account.create(resourceGroupName, accountName, accountToCreate, function (err, result, request, response)
//other code here
});
Taking a look at how ms-rest-azure's msRestAzure.interactiveLogin function is written, it appears that there's a "domain", or tenant, parameter that you can pass in the event that you are a member of more than one Azure Active Directory (tenant).
You should pass in the tenant that is tied to your subscription. This should be given to you in the full, current error message that you get. The tenant may look like "contoso.com", "contoso.onmicrosoft.com", or it could be a GUID.
This disambiguates your authentication call by explicitly mentioning which directory should be used.
I hope this helps!

Resources