Unable to get the MSI credentials with NodeJs - node.js

I am working with the NodeJs Azure Function V2 and I want to get the secret from Key-Vault.
I tried with following reference. Here's a link.
I am using ms-rest-azure NPM library package.
My code as follows:
function getKeyVaultCredentials(){
return msRestAzure.loginWithAppServiceMSI({resource: "https://my-keyvault-DNS-url.vault.azure.net",msiEndpoint: process.env["MSI_ENDPOINT"],msiSecret:process.env["MSI_SECRET"]});
}
function getKeyVaultSecret(credentials) {
var keyVaultClient = new KeyVault.KeyVaultClient(credentials);
return keyVaultClient.getSecret("https://my-keyvault-DNS-url.vault.azure.net", 'secret', "mySecretName");
}
getKeyVaultCredentials().then(
getKeyVaultSecret
).then(function (secret){
console.log(`Your secret value is: ${secret.value}.`);
}).catch(function (err) {
throw (err);
});
The function call executed successfully but never getting the credential.
Note :
I have enabled the MSI identity and given access to kevault for that Azure function.
The error I am getting is as follows:
MSI: Failed to retrieve a token from "http://127.0.0.1:410056/MSI/token/?resource=https://my-keyvault-DNS-url.vault.azure.net&api-version=2017-09-01" with an error: {"ExceptionMessage":"AADSTS500011: The resource principal named https://my-keyvault-DNS-url.vault.azure.net was not found in the tenant named 6620834b-d11e-44cb-9931-2e08b6ee81cc00. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\nTrace ID: 1f25ac6c-01e0-40d8-8146-269f22d49f001\r\nCorrelation ID: 4beede0c-2e83-4bcc-944d-ba4e8ec2c6834\r\nTimestamp: 2019-03-29 02:54:40Z","ErrorCode":"invalid_resource","ServiceErrorCodes":["500011"],"StatusCode":400,"Message":null,"CorrelationId":"e6e8108d-e605-456b-8fb6-473962dcd5d678"}
I might doing some silly/blunder - please help!!

There are some subtle fixes that your code needs
resource should be set to https://vault.azure.net. This basically has to be the resource in general, not your instance as such.
The method is actually getSecret('<KEYVAULT_URI>', '<SECRET_NAME>', '<SECRET_VERSION>')
Here is how your code should look like at the end
function getKeyVaultCredentials() {
return msRestAzure.loginWithAppServiceMSI({
resource: 'https://vault.azure.net'
});
}
function getKeyVaultSecret(credentials) {
var keyVaultClient = new KeyVault.KeyVaultClient(credentials);
return keyVaultClient.getSecret(
'https://my-keyvault-DNS-url.vault.azure.net',
'mySecretName',
''
);
}
getKeyVaultCredentials()
.then(getKeyVaultSecret)
.then(function(secret) {
console.log(`Your secret value is: ${secret.value}.`);
})
.catch(function(err) {
throw err;
});

Related

Node.JS PowerBI App Owns Data for Customers w/ Service Principal (set "config.json" from a table in my database)

I'm attempting to refactor the "Node.JS PowerBI App Owns Data for Customers w/ Service Principal" code example (found HERE).
My objective is to import the data for the "config.json" from a table in my database and insert the "workspaceId" and "reportId" values from my database into the "getEmbedInfo()" function (inside the "embedConfigServices.js" file). Reason being, I want to use different configurations based on user attributes. I am using Auth0 to login users on the frontend, and I am sending the user metadata to the backend so that I can filter the database query by the user's company name.
I am able to console.log the config data, but I am having difficulty figuring out how to insert those results into the "getEmbedInfo()" function.
It feels like I'm making a simple syntax error somewhere, but I am stuck. Here's a sample of my code:
//----Code Snippet from "embedConfigServices.js" file ----//
async function getEmbedInfo() {
try {
const url = ;
const set_config = async function () {
let response = await axios.get(url);
const config = response.data;
console.log(config);
};
set_config();
const embedParams = await getEmbedParamsForSingleReport(
config.workspaceId,
config.reportId
);
return {
accessToken: embedParams.embedToken.token,
embedUrl: embedParams.reportsDetail,
expiry: embedParams.embedToken.expiration,
status: 200,
};
} catch (err) {
return {
status: err.status,
error: err.statusText,
}
};
}
}
This is the error I am receiving on the frontend:
"Cannot read property 'get' of undefined"
Any help would be much appreciated. Thanks in advance.
Carlos
The error is because of fetching wrong URL. The problem is with the config for the Service Principal. We will need to provide reportId, workspaceId for the SPA and also make sure you added the service principal to workspace and followed all the steps from the below documentation for the service principal authentication.
References:
https://learn.microsoft.com/power-bi/developer/embedded/embed-service-principal

Unable to exchange auth-code to get access-token using Microsoft identity platform in my node application

I am implementing OAuth2 using Microsoft identity platform. For this purpose, I registered my app to Azure Active Directory admin center and got an app-id and other app credentials. Redirect-url also mentioned as well.
So first of all, I generated auth-url (code snippet is provided below) and got an auth-code to my redirect endpoint. when I try to exchange auth-code to get access-token, it throws as exception with statuscode 401 with error message "401 Unauthorized".
I am using simple-oauth2 node package.
Here is my snippet to generate auth-url
function authUrl() {
const url = oauth2.authorizationCode.authorizeURL({
redirect_uri: outlookCalendar.REDIRECT_URI,
scope: outlookCalendar.APP_SCOPES
});
return url;
}
where APP_SCOPES = "User.Read https://outlook.office.com/Calendars.ReadWrite"
here is auth-url generated by above code
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id=xxxxxxxxxxxx&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcalendars%2Foutlook%2Fcode&scope=User.Read%20https%3A%2F%2Foutlook.office.com%2FCalendars.ReadWrite
here is the function to get access-token
async function accessToken(code) {
try {
const result = await oauth2.authorizationCode.getToken({
code : code,
redirect_uri: outlookCalendar.REDIRECT_URI,
scope: outlookCalendar.APP_SCOPES
});
const token = oauth2.accessToken.create(result);
console.log('Token created: ', token.token);
return { tokens: token.token.access_token };
} catch (exception) {
console.log(exception);
return { error: exception };
}
}
So, Please anyone tell me the way to get access token or something was going wrong in my code.
Thank you...

How to access Key Vault with Azure Managed Service Identity in node?

I follow the instruction here to create an Managed Service Identity. So now in my environment variable, I have MSI_ENDPOINT and MSI_SECRET.
In my typescript (node.js) project, I imported the following project:
import {KeyVaultCredentials, KeyVaultClient} from "azure-keyvault";
import {AuthenticationContext, ErrorResponse, TokenResponse} from "adal-node";
If I wasn't using MSI, I could access my key vault using the following code:
let keyVaultCredentials = new KeyVaultCredentials(KeyVault.createAuthenticator(this.clientID, this.clientKey));
let keyVaultClient = new KeyVaultClient(keyVaultCredentials);
private static createAuthenticator(clientID: string, clientKey: string){
return (challenge, callback) => {
let context = new AuthenticationContext(challenge.authorization);
return context.acquireTokenWithClientCredentials(
challenge.resource,
clientID,
clientKey,
function (err, tokenResponse:TokenResponse | ErrorResponse) {
if (err) {
CLogger.log("error", "Error occurred while acquiring token with key vault credentials: " + JSON.stringify(err));
throw new Error("Error occurred while acquiring token with key vault credentials. Check log files");
}
if(<TokenResponse>tokenResponse){
let authorizationValue = (<TokenResponse>tokenResponse).tokenType + " " + (<TokenResponse>tokenResponse).accessToken;
return callback(null, authorizationValue);
}
});
}
}
I have no idea how to get access token with MSI enabled, please help.
With the new Azure SDK for js, you can authenticate your application with managed service by implementing class DefaultAzureCredential from package #azure/identity.
const {DefaultAzureCredential} = require('#azure/identity');
const {SecretClient} = require('#azure/keyvault-secrets');
const credential = new DefaultAzureCredential();
const vaultName = "<key-vault-name>";
const url = `https://${vaultName}.vault.azure.net`;
const client = new SecretClient(url, credential);
client.setSecret(secretName, "MySecretValue");
........
It supports both service principal and managed identity authentication.
To run it on a local environment you must set three environment variables: AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET to be able to connect with a service principal.
On Azure, if those variables are not defined, it will try to authenticate with managed identity.
There is a quickstart guide here.
Using the loginWithAppServiceMSI() method from ms-rest-azure will autodetect if you're on a WebApp and get the token from the MSI endpoint. Then, the code is simply:
function getKeyVaultCredentials(){
return msRestAzure.loginWithAppServiceMSI({resource: 'https://vault.azure.net'});
}
function getKeyVaultSecret(credentials) {
let keyVaultClient = new KeyVault.KeyVaultClient(credentials);
return keyVaultClient.getSecret(KEY_VAULT_URI, 'secret', "");
}
getKeyVaultCredentials().then(
getKeyVaultSecret
).then(function (secret){
console.log(`Your secret value is: ${secret.value}.`);
}).catch(function (err) {
throw (err);
});
I'd recommend checking the full documentation here

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();
}
});
});
};

OpenID OWIN auth and lack of user permissions

I may be handling this totally incorrect, but I am using OpenID with MS Azure to authentication my users, then I check to make sure the user has a user account in the notifications of the OpenID middleware, if the user is not found, I am throwing a security exception. How do I return a You do not have access to this applicaiton type page. Am I just missing the hook?
Here is the example:
https://gist.github.com/phillipsj/3200ddda158eddac74ca
You can use try...catch inside the notifications, something along these lines:
SecurityTokenValidated = (context) =>
{
try
{
// retriever caller data from the incoming principal
var username = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value.Split('#')[0];
var database = DependencyResolver.Current.GetService(typeof (IDatabase)) as IDatabase;
var employee = database.Query(new GetEmployeeByUsername(username));
if (employee == null)
{
throw new SecurityTokenValidationException();
}
// I add my custom claims here
context.AuthenticationTicket.Identity.AddClaims(claims);
return Task.FromResult(0);
}
catch (SecurityTokenValidationException ex)
{
context.HandleResponse(); // This will skip executing rest of the code in the middleware
context.Response.Redirect(....);
return Task.FromResult(0);
}
}

Resources