How to call Azure Client from Node via https request? - node.js

I have VM up and running in Azure. I wanted to call Azure VM from Node. I am totally unware about the get API for Azure. So how can this be achieved?
I have tenantID, ClientID, Client Secret and subscription id.
Also is there any thing unique I can get from running VM which will be same if I create another VM on Azure?

Try this considering you have the values in to access vault in environment variable to get secrets from azure vault:
var _ = require('lodash'),
msRestAzure = require('ms-rest-azure'),
KeyVault = require('azure-keyvault'),
AuthenticationContext = require('adal-node').AuthenticationContext,//Utility function to get key vault client
function getKeyVaultClient() {
// service principal details to access the vault
var clientId = process.env['CLIENT_ID']; // service principal
var domain = process.env['DOMAIN']; // tenant id
var secret = process.env['APPLICATION_SECRET'];
return new Promise(function (resolve, reject) {
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain, function (err) {
if (err) {
return reject(err);
}
// authenticate with key vault with a service principal
var kvCredentials = new KeyVault.KeyVaultCredentials(authenticator);
keyVaultClient = new KeyVault.KeyVaultClient(kvCredentials);
return resolve(keyVaultClient);
});
})
}
function authenticator(challenge, callback) {
// service principal details to access the vault
var clientId = process.env['CLIENT_ID']; // service principal
var secret = process.env['APPLICATION_SECRET'];
// Create a new authentication context.
var context = new AuthenticationContext(challenge.authorization);
// Use the context to acquire an authentication token.
return context.acquireTokenWithClientCredentials(challenge.resource, clientId, secret, function (err, tokenResponse) {
if (err) {
return callback(err);
}
// Calculate the value to be set in the request's Authorization header and resume the call.
var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken;
return callback(null, authorizationValue);
});
}
var vaultUri = 'https://' + process.env['VAULT_NAME'] + '.vault.azure.net/secrets/' + key;
getKeyVaultClient().then(function (vaultClient) {
vaultClient.getSecret(vaultUri,
function (err, result) {
if (err) {
return reject(err);
} else {
return resolve(result.value);
}
});
}).catch(function (err) {
reject(err);
})
You can have a look at the official documentation and examples on how to access keys and secrets using the Rest API's exposed by azure.

Related

How to grab value from promise in Nodejs

Hi I am writing a nodejs code in Azure functions to capture the username saved in Azure key vault.
Here is the code I have written
module.exports = async function (context, req) {
var msRestAzure = require('ms-rest-azure');
var KeyVault = require('azure-keyvault');
function getKeyVaultCredentials() {
return msRestAzure.loginWithAppServiceMSI({
resource: 'https://vault.azure.net/'
});
}
function getKeyVaultSecret(credentials) {
let keyVaultClient = new KeyVault.KeyVaultClient(credentials);
return keyVaultClient.getSecret('https://myDNS.vault.azure.net/', 'username', '');
}
const username = getKeyVaultCredentials()
.then(getKeyVaultSecret)
.then(function (secret){
context.log(`Your secret value is: ${secret.value}.`);
return secret.value;})
.catch(function (err) {throw (err);});
context.log(username)
context.res = {
body: username
};
}
I want to capture the username but it is giving me output as
promise {pending}
How to wait for the function to end so that I can extract the username.
I am very new nodejs, please let me know what wrong I am doing and what should be the exact solution.
Thanks
Actually you have already used then to get the secret value. The value will be returned if there is no issues with the dependencies and configurations.
getKeyVaultCredentials()
.then(getKeyVaultSecret)
.then(function (secret){
context.log(`Your secret value is: ${secret.value}.`);
return secret.value;})
.catch(function (err) {throw (err);});
But you will encounter some issues when using this sdk and here is the github issue for your reference.
It is recommended that you use the new Azure Key Vault SDK instead. It is more convenient to use. Here are the detailed steps to use MSI and Key vault in Azure function.

ADAL: Where do I view the resource ID's?

I'm new to using adal-node npm package.
In the example it mentions:
var resource = '00000002-0000-0000-c000-000000000000';
Where is this ID from? From my use-case, I just want to batch update users in my AD.
A resource is a service/app/api for which you ask an Identity provider like Azure AD to issue tokens for. You can also you the AppId (client Id) as a resource as well
'00000002-0000-0000-c000-000000000000' is the client Id of Azure AD Graph API (https://graph.windows.net/ ). This Api is being deprecated in favor of Microsoft Graph "https://graph.microsoft.com", which is the resource you should use.
The value of the resource is a URI that identifies the resource for which the token is valid.
If you want to use Microsoft Graph API to update your users in your AD, you should use https://graph.microsoft.com for the resource.
You can refer to this sample. You will get the graph client to update the users.
const AuthenticationContext = require('adal-node').AuthenticationContext;
const MicrosoftGraph = require("#microsoft/microsoft-graph-client");
const authorityHostUrl = 'https://login.windows.net';
const tenantName = ''; //azure active directory tenant name. ie: name.onmicrosoft.com
const authorityUrl = authorityHostUrl + '/' + tenantName;
const applicationId = ''; //application id for registered app
const clientSecret = ''; //azure active directory registered app secret
const resource = "https://graph.microsoft.com"; //URI of resource where token is valid
const context = new AuthenticationContext(authorityUrl);
context.acquireTokenWithClientCredentials(
resource,
applicationId,
clientSecret,
function(err, tokenResponse) {
if (err) {
console.log('well that didn\'t work: ' + err.stack);
} else {
let client = MicrosoftGraph.Client.init({
defaultVersion: 'v1.0',
authProvider: (done) => {
done(null, tokenResponse.accessToken);
},
});
client
.api('/users')
.get((err, result) => {
console.log(result, err);
});
}
});

Authentication Error when Retrieving and Editing Device Configuration on IoT-Core

I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs
However, I keep getting error:
code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED".
I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?
Code below was how I invoked the request
function createJwt (projectId, privateKeyFile, algorithm) {
// Create a JWT to authenticate this device. The device will be disconnected
// after the token expires, and will have to reconnect with a new token. The
// audience field should always be set to the GCP project ID.
const token = {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
'aud': projectId
};
const privateKey = fs.readFileSync(privateKeyFile);
return jwt.sign(token, privateKey, { algorithm: algorithm });
}
app.get('/', function(req, res){
let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');
const options = {
url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
headers: {
'authorization': 'Bearer ' + authToken,
'content-type': 'application/json',
'cache-control': 'no-cache'
},
json: true
}
request.get(options, function(error, response){
if(error) res.json(error);
else res.json(response);
})
});
For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device
I was able to retrieve and update device configurations using the code below
function getClient (serviceAccountJson, cb) {
const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const API_VERSION = 'v1';
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
google.discoverAPI(discoveryUrl, {}, (err, client) => {
if (err) {
console.log('Error during API discovery', err);
return undefined;
}
cb(client);
});
}
function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;
const request = {
name: `${registryName}/devices/${deviceId}`
};
const promise = new Promise(function(resolve, reject){
client.projects.locations.registries.devices.get(request, (err, data) => {
if (err) {
console.log('Could not find device:', deviceId);
console.log(err);
reject(err);
} else {
console.log(data.config.binaryData);
resolve(data);
}
});
});
return promise;
}
app.get('/', function(req, res){
const cb = function(client){
getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
.then(function(response){
let decoded = new Buffer(response.config.binaryData, 'base64').toString();
res.json(decoded);
})
.catch(function(error){
res.json(error);
})
}
getClient(serviceAccountJson, cb);
});
I think what you're looking to do is best accomplished using the client library for NodeJS.
First, retrieve an API client object as done in the sample. This will take in the service account credentials you used and will authenticate against Google API Core servers.
At the point in the referenced code where cb(client); is invoked, you'll have your client object and are ready to update your device. Add the imports and API constants from the sample and replace the code where you have a client object with the following code and you should be set.
Use some strings for your device identifiers:
const projectId = 'my-project';
const cloudRegion = 'us-central1';
const registryId = 'my-registry';
const deviceId = 'my-device;
const config = '{fan: 800}';
Next, form your device String:
const deviceId = `projects/${projectId}/locations/${cloudRegion}/registries/${registryId}/devices/${deviceId}`;
const binaryData = Buffer.from(config).toString('base64');
Now you form your request object and execute:
const request = {
name: `${registryName}`,
versionToUpdate: 0,
binaryData: binaryData
};
console.log(request);
client.projects.locations.registries.devices
.modifyCloudToDeviceConfig(
request,
(err, data) => {
if (err) {
console.log('Could not update config:', deviceId);
console.log('Message: ', err);
} else {
console.log('Success :', data);
}
});
Your configuration is updated. If your device is subscribed to the config topic on MQTT it will receive the latest configuration, otherwise, you can poll for the configuration with HTTP from your device.
Just to confirm, when you created the SSL key pair, and when you registered the device with the Cloud IoT Core registry, did you match the type of key created with the radio button you registered it with?
Also to confirm, you put the Google root certificate on the device in the same directory as the private key: ./keys/device-config.pem ? If not you can fetch it with: wget https://pki.google.com/roots.pem.

NodeJS Example - Firebase Cloud Functions - Instantiate an Admin SDK Directory service object

Goal
Use googleapis with Firebase Cloud Functions to get a list of all users in my G Suite domain.
Question
How do I Instantiate an Admin SDK Directory service object. I do not see a NodeJS example, and I'm not clear how to setup and make the request with googleapis.
Context
This code runs from Firebase Cloud Functions, and it seems to authenticate okay. Now, how do I setup the service object at //TODO in the following code:
// Firebase Admin SDK
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
// Google APIs
const googleapis = require('googleapis')
const drive = googleapis.drive('v3')
const gsuiteAdmin = googleapis.admin('directory_v1')
// Service Account Key - JSON
let privatekey = require("./privatekey.json")
let jwtClient = new googleapis.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
['https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/admin.directory.user'])
// Firebase Cloud Functions - REST
exports.authorize = functions.https.onRequest((request, response) => {
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err)
return
} else {
console.log("Successfully connected!")
}
// TODO
// USE SERVICE OBJECT HERE??
// WHAT DOES IT LOOK LIKE?
response.send("Successfully connected!")
})
})
Order of Operations:
Create Service Account Credentials in Google Cloud Console
Add Domain-Wide Delegation to the Service Account
Authorize the API in G Suite - Security - Advanced
Go back to the Service Account and Download the .json key file
I downloaded the .json key file too soon, e.g., before authorizing the APIs in G Suite. The order, Setting up the Service Account with DwD and then authorization the API in G Suite API and then downloading the .json key file is important.
The Example
// Firebase Admin SDK
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
// Google APIs
const googleapis = require('googleapis')
const drive = googleapis.drive('v3')
const directory = googleapis.admin('directory_v1')
// Service Account Key - JSON
let privatekey = require("./privatekey.json")
let impersonator = 'example#example.com'
let jwtClient = new googleapis.auth.JWT(
privatekey.client_email,
null, // not using path option
privatekey.private_key,
['https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.user.readonly'],
impersonator
)
// Firebase Cloud Functions - REST
exports.getUsers = functions.https.onRequest((request, response) => {
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err)
return
} else {
console.log("Successfully connected!")
}
//Google Drive API
directory.users.list ({
auth: jwtClient,
domain: 'example.com',
maxResults: 10,
orderBy: 'email',
viewType: 'domain_public'
}, function(err, res) {
if (err) {
console.log('The API returned an error: ' + err)
return;
}
var users = res.users;
if (users.length == 0) {
console.log('No users in the domain.');
} else {
console.log('Users:');
for (var i = 0; i < users.length; i++) {
var user = users[i];
console.log('%s (%s)', user.primaryEmail, user.name.fullName)
}
response.send(users)
}
})
})
})
UPDATE
The example above is not secure. A Cloud Function, especially with G Suite Domain-wide Delegation, should not respond to http requests unless they come from your application. See in this example that the Cloud Function uses admin.auth().verifyIdToken(idToken)... to validate that the request is authenticated by Firebase.
If you don't properly handle your G Suite DwD Cloud Function, you risk exposing your G Suite API to the public.

users.list returns 403 Error: Not Authorized to access this resource/api

I am trying to retrieve a list of users using the node.js googleapis library and a service account.
I followed this guide to 'Perform Google Apps Domain-Wide Delegation of Authority'. There are examples for Java and Python, but unfortunately not for node.js, which seems to work rather differently.
I tried following the quickstart and completed the first two steps, but then it uses a manual OAuth flow instead of a service account.
So I tried to follow the example here to authorize using a service account. That all seems to work until I send the request, then I get an error: Error: Not Authorized to access this resource/api with code: 403.
Here's my code:
var google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1')
authFactory.getApplicationDefault(function (err, authClient) {
console.log('GOT APPLICATION DEFAULT', authClient)
if (err) {
console.log('Authentication failed because of ', err);
return;
}
if (authClient.createScopedRequired && authClient.createScopedRequired()) {
console.log('SCOPE REQUIRED')
var scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
authClient = authClient.createScoped(scopes);
}
var request = {
auth: authClient,
domain: 'mydomain.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
});
What have I missed please?
After several hours of experimenting I came to the conclusion that this particular API cannot be accessed with a service account. Although it is not explicitly stated in the docs anywhere that I could find, the quickstart seems to overcome this limitation by using an OAuth process and then storing in a file the tokens required to authorize future requests. If I'm wrong please add a better answer!
My solution is to use the quickstart project to generate those tokens and then add the credentials and tokens from the quickstart to my project and use them whenever my server starts, something like:
let tokens = require('./credentials/tokens.json'),
credentials = require('./credentials/oauth_credentials.json'),
clientSecret = credentials.installed.client_secret,
clientId = credentials.installed.client_id,
redirectUrl = credentials.installed.redirect_uris[0],
google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1'),
oauth2Client = new authFactory.OAuth2(clientId, clientSecret, redirectUrl);
oauth2Client.credentials = tokens;
let request = {
auth: oauth2Client,
domain: 'coachaxis.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
It's not elegant but it works.

Resources