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

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

Related

Keyvault Authentication using username and password

The customer has created a key vault and store the credentials . To authenticate key vault , I have created the application in the node and using client id and client secret , I am able to read the secrets. But now the customer wants not to use the client id and client secret , instead use the username and password of the AZURE to access the keyvault in the program. Its one dedicated user for the keyvault access with no MFA.
I am not sure if we can access the keyvault with username and password from the node js. Kindly suggest.
Thanks
For this requirement, I also think that use username-password flow is unnecessary and client credential flow should be better (as juunas mentioned in comments). But if the customer still want to use username-password flow to implement, I can provide a sample as below for your reference:
1. You should register an app in AD with native platform but not web platform.
And please check if "Treat application as a public client" is enabled.
If your app is web platform, when you run the node js code it will show error message to ask you provide "client secret" even if you use username-password flow.
2. You need to add the azure key vault permission to your app.
And do not forget grant admin consent for it.
3. Then you can refer to the code below to get the secret value.
const KeyVault = require('azure-keyvault');
const { AuthenticationContext } = require('adal-node');
const clientId = '<clientId>';
const username = '<username>';
const password = '<password>';
var secretAuthenticator = function (challenge, callback) {
var context = new AuthenticationContext(challenge.authorization);
return context.acquireTokenWithUsernamePassword(challenge.resource, username, password, clientId, function(
err,
tokenResponse,
) {
if (err) throw err;
var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken;
return callback(null, authorizationValue);
});
};
var credentials = new KeyVault.KeyVaultCredentials(secretAuthenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.getSecret("https://<keyvaultname>.vault.azure.net/", "<secret name>", "<secret version>", function (err, result) {
if (err) throw err;
console.log("secret value is: " + result.value);
});

Is it possible to create a secret_id to use it in an approle automatically without the help of an administrator or kubernetes when using vault?

Recently searching the internet I found a good alternative to manage the secrets of my application created in node js with the help of hashicorp vault. I have investigated how it works and among the possible ways that this tool has to enter I found approle, which I consider an adequate form of authentication through my application. This form of authentication requires a role_id and a secret_id. The latter, as I see in the examples of the official vault page, needs an entity for its creation and then passes it to the application and in this way the application can receive the token to enter the vault. Currently I have this code in node js that receives a token wrapped with the secret_id to achieve access to the secrets with the role of the application:
//get the wrap token from passed in parameter
var wrap_token = process.argv[2];
if(!wrap_token){
console.error("No wrap token, enter token as argument");
process.exit();
}
var options = {
apiVersion: 'v1', // default
endpoint: 'http://127.0.0.1:8200',
token: wrap_token //wrap token
};
console.log("Token being used " + process.argv[2]);
// get new instance of the client
var vault = require("node-vault")(options);
//role that the app is using
const roleId = '27f8905d-ec50-26ec-b2da-69dacf44b5b8';
//using the wrap token to unwrap and get the secret
vault.unwrap().then((result) => {
var secretId = result.data.secret_id;
console.log("Your secret id is " + result.data.secret_id);
//login with approleLogin
vault.approleLogin({ role_id: roleId, secret_id: secretId }).then((login_result) => {
var client_token = login_result.auth.client_token;
console.log("Using client token to login " + client_token);
var client_options = {
apiVersion: 'v1', // default
endpoint: 'http://127.0.0.1:8200',
token: client_token //client token
};
var client_vault = require("node-vault")(client_options);
client_vault.read('secret/weatherapp/config').then((read_result) => {
console.log(read_result);
});
});
}).catch(console.error);
The problem is that I plan to upload the application in the cloud using docker and the idea is that the process of obtaining the secrets is automatic so I would like to know if when creating a token that lasts long enough that you only have the possibility of obtaining the secret_id of a role and saving it as environment variable is appropriate in this case or if there is any other alternative that can help me in automating this case.
Note: I don't plan to deploy in aws in this case.

How to extract Secret key from Azure key vault in Azure Function App on Nodejs stack

I have created an Azure Function app in Nodejs version 12. My hosting environment is windows. What is the easiest way to capture the username and password which are saved in Azure key vault inside my function.
Also I am using Inline code Editor so how should be capture the credentials in code.
Thanks
The node SDK used in above answer is going to be deprecated and won't have new feature and releases. Instead, the new versions are released here:
https://www.npmjs.com/package/#azure/keyvault-secrets
Here are the detailed steps to retrieve the secret value for your reference.
1.Enable system assigned managed identity in your function.
2.Add this service principal to the access policy of your key vault.
3.Install the dependencies to your function.
"dependencies": {
"#azure/identity": "^1.0.3",
"#azure/keyvault-secrets": "^4.0.4"
}
4.Here is my testing function code
module.exports = async function (context, req) {
const { DefaultAzureCredential } = require("#azure/identity");
const { SecretClient } = require("#azure/keyvault-secrets");
const keyVaultName = "tonykeyvault20190801";
const KVUri = "https://" + keyVaultName + ".vault.azure.net";
const credential = new DefaultAzureCredential();
const client = new SecretClient(KVUri, credential);
const retrievedSecret = await client.getSecret("username");
const username=retrievedSecret.value;
context.log(username);
context.res = {
body: username
};
}
5.The execution result.

Unable to get the MSI credentials with NodeJs

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

Azure API failed to authenticate the request

I am using the next code to get the token for Azure AD authentication
errorMessage = "";
AuthenticationResult result = null;
var context = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["login"], ConfigurationManager.AppSettings["tenantId"]),false);
ClientCredential clientCredential = new ClientCredential(ConfigurationManager.AppSettings["clientId"], ConfigurationManager.AppSettings["key"]);
try
{
result = context.AcquireToken(ConfigurationManager.AppSettings["apiEndpoint"], clientCredential);
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
errorMessage = "Temporarily Unavailable";
return null;
}
else
{
errorMessage = "Unknown Error";
return null;
}
}
string token = result.AccessToken;
var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"],token);
//string certificateString = ConfigurationManager.AppSettings["managementCertificate"];
//var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
return credential;
After that I am doing the next to create a website in Azure
using (var computeClient = new WebSiteManagementClient(credentials))
{
var result = computeClient.WebSites.IsHostnameAvailable(websiteName);
if (result.IsAvailable)
{
await computeClient.WebSites.CreateAsync(WebSpaceNames.WestEuropeWebSpace, new WebSiteCreateParameters() {
Name= websiteName,
ServerFarm= ConfigurationManager.AppSettings["servicePlanName"]
});
}
else
{
return ResultCodes.ObjectNameAlreadyUsed;
}
}
But every time I execute that I got the following error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I tried to import the management certificate as they said here:
https://www.simple-talk.com/cloud/security-and-compliance/windows-azure-management-certificates/
And also tried this one:
http://technetlibrary.com/change-windows-azure-subscription-azure-powershell/198
For importing management certificate.
Also I gave the application permissions to access management API.
Azure AD Authentication DOES NOT use the management certificate authentication.
There is a good documentation and code sample on MSDN on how to resolve your current issue.
Authenticating Service Management Requests
Looks like your application does not have permission to access the Azure API's.
Please use this link to get permissions.
After this please add permissions to access API in app permission or user permission.

Resources