Azure Default Credential with Managed Identity not working (node) - node.js

I was trying to use a user-assigned managed identity with the DefaultAzureCredential, but am getting the 403 permissions mismatch error. I'm following the code example found in MS docs and it still fails. However, replacing the DefaultAzureCredential with the explicit ManagedIdentityCredential works just fine.
This is my code:
const { BlobServiceClient } = require('#azure/storage-blob');
const { DefaultAzureCredential } = require('#azure/identity');
const {url, clientId} = require('./config');
const cred = new DefaultAzureCredential({managedIdentityClientId: clientId});
const containerClient = new BlobServiceClient(url, cred).getContainerClient('container-name');
(async () => {
const exists = await containerClient.exists();
console.log({exists});
})();
This looks like it should work, but it does not. Any thoughts?
versions:
"#azure/identity": "^1.1.0",
"#azure/storage-blob": "^12.12.0",
node v16.18.1

I tried in my environment and got below results:
I tried to reproduce same code in my environment, and it successfully executed with container exist or not.
Code:
const { BlobServiceClient } = require('#azure/storage-blob');
const { DefaultAzureCredential } = require('#azure/identity');
const url="https://venkat123.blob.core.windows.net";
const clientId="<client-id>";
const cred = new DefaultAzureCredential({managedIdentityClientId: clientId});
const Client = new BlobServiceClient(url, cred);
const containerClient=Client.getContainerClient("test");
(async () => {
const exists = await containerClient.exists();
console.log({exists});
})();
Console:
403, This request is not authorized to perform this operation using this permission.
If you are accessing storage account with identity, you need a role like Storage-blob-contributor or storage-blob-owner.
Go to portal -> your storage account -> Access Control (IAM) ->Add -> Add role assignments -> storage-blob-contributor or storage-blob-owner->Add your user managed identity id.
Also, I tried with user-assigned managed identity with the DefaultAzureCredential it worked perfectly.
Code:
const { BlobServiceClient } = require('#azure/storage-blob');
const { DefaultAzureCredential } = require('#azure/identity');
const url="https://venkat123.blob.core.windows.net";
const cred = new DefaultAzureCredential();
const Client = new BlobServiceClient(url, cred);
const containerClient=Client.getContainerClient("test");
(async () => {
const exists = await containerClient.exists();
console.log({exists});
})();
Console:

Related

Cannot upload file to Azure Blob Node.js

Trying to upload a file to azure blob storage using #azure/storage-blob sdk in nodejs:
module.exports.createBlob = (blobName, containerName, blobContent) => {
return new Promise(async (resolve, reject) => {
try {
const sharedKeyCredential = await this.createSharedAccessToken(blobName, 'c')
const blobServiceClient = new BlobServiceClient(
`https://${process.env.AZURE_BLOB_ACCOUNT}.blob.core.windows.net`,
sharedKeyCredential
)
const containerClient = blobServiceClient.getContainerClient(containerName)
const blockBlobClient = containerClient.getBlockBlobClient(blobName)
const blob = await blockBlobClient.upload(blobContent, blobContent.length) // implement later
resolve(blob)
} catch (err) {
console.log(err)
reject(err)
}
})
}
module.exports.createSharedAccessToken = (blobName, permission) => {
return new Promise(async (resolve, reject) => {
const sharedKeyCredential = new StorageSharedKeyCredential(process.env.AZURE_BLOB_ACCOUNT, process.env.AZURE_BLOB_KEY)
const containerName = process.env.AZURE_CONTAINER_NAME
const startsOn = new Date()
expiresOn.setMinutes(expiresOn.getMinutes() + parseInt(autoLogoutDuration.KeyValue))
const blobSAS = generateBlobSASQueryParameters({
containerName, // Required
blobName, // Required
permissions: BlobSASPermissions.parse(permission), // Required
startsOn: startsOn, // Required
},
sharedKeyCredential // StorageSharedKeyCredential - `new StorageSharedKeyCredential(account, accountKey)`
).toString()
resolve(decodeURI(blobSAS))
})
}
It keeps throwing a "NoAuthenticationInformation" error. The same creds work for downloading an existing blob but uploading is not working no matter what I try. Any help would be appreciated.
Followed by this MS DOC , I tried to reproduce your issue ,but able to upload files into my azure blob container using Node.js without any authentication error.
As you are using shared key credential we need to have all those permissions in our portal as shown below:
Also i am using #azure/storage-blob sdk in nodejs in my package.json .
Also added #azure/storage-blob sdk in my testupload.js file
And added the below code into my testupload.js file as i have already created container i just commented the above screenshot code .
const account="testa";
const sharedKeyCredential = new StorageSharedKeyCredential("yourstorageaccountname", "your storage key connection string");
const blobServiceClient1 = new BlobServiceClient(
// When using AnonymousCredential, following url should include a valid SAS or support public access
`https://${account}.blob.core.windows.net`,
sharedKeyCredential
);
const blobnewname="example2.txt"
blobContent="hi hello";
const containerClient1 = blobServiceClient.getContainerClient("test")
const blockBlobClient1= containerClient.getBlockBlobClient(blobnewname)
const blob = blockBlobClient1.upload(blobContent, blobContent.length)
Then i can able to test and upload my files to Azure blob container
Here are screenshot for reference:
For more information please refer this MS DOC : Azure Storage samples using v12 JavaScript client libraries
UPDATE:-
As suggested by #Setanjan Roy
Alternate way:-
To get rid of this we can use new BlobServiceClient( https://${process.env.AZURE_BLOB_ACCOUNT}.blob.core.windows.net${sharedKeyCredential} )

Is it possible to use service principal to access blob storage?

I am now trying to use Service Principal to access azure blob storage in nodes, instead of using connection string.
What I did (and succeeded) is using connection string as follows:
// connect via connection string
const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING;
const blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING);
Now I want to use Service Principal instead of connection string, but I can't seem to make it work. I can see some examples using some token credentials, e.g.
const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
defaultAzureCredential
);
Is it possible to use service principal credentials this way, or are there other ways to do this?
Try this :
const { BlobServiceClient } = require("#azure/storage-blob");
const { ClientSecretCredential } = require("#azure/identity");
const account = '<your accounr name>'
//Using Service Principal
const appID = ""
const appSec = ""
const tenantID = ""
const clientCred = new ClientSecretCredential(tenantID,appID,appSec)
const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
clientCred
);
//try to list all containers in stroage account to check if success
blobServiceClient.listContainers().byPage().next().then(result =>{
result.value.containerItems.forEach(element => {
console.log(element.name);
});
})
Result:
Note:
Before you run this demo, pls make sure that you have granted the required permissions to your Service Principal, details see this official doc.

How to use Azure npm packages without async?

Due to depreciation, I am trying to update my code to the following 2 packages:
https://www.npmjs.com/package/#azure/storage-blob
https://www.npmjs.com/package/#azure/cosmos
The issue I am having is that my code currently uses packages that were pre async/await, whereas all the documentation for the newer packages assume use of async functions.
I would like to update my code's functions but without restructuring it with async functions. Is there any documentation out there on how to do that? Or any clear and easy examples?
For example, I am using this example to upload a blob:
const { DefaultAzureCredential } = require("#azure/identity");
const { BlobServiceClient } = require("#azure/storage-blob");
const account = "<account>";
const defaultAzureCredential = new DefaultAzureCredential();
const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
defaultAzureCredential
);
const containerName = "<container name>";
async function main() {
const containerClient = blobServiceClient.getContainerClient(containerName);
const content = "Hello world!";
const blobName = "newblob" + new Date().getTime();
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.upload(content, content.length);
console.log(`Upload block blob ${blobName} successfully`, uploadBlobResponse.requestId);
}
main();
I would like to do so without async/await, and for blockBlobClient.upload use function(err, result){ do stuff} as my code already has.
Similarly, I would like to do so with #azure/cosmos functions.
My current packages are:
https://www.npmjs.com/package/documentdb
https://www.npmjs.com/package/azure-storage
Is this possible?
I would really prefer not to restructure all my code...
Thanks
For any methdods that returns a promise, you can still use the call back approach:
blockBlobClient.upload().then((response) => {
// do something with the response...
});

Unable to regenerate storage key with Azure Management API

I can't use /regenerateKey [1] to regenerate keys for a Storage Account with the Azure Management API.
I'm using the following code in JavaScript (the resource has the subscription removed)
const { ClientSecretCredential } = require('#azure/identity');
const { SecretClient } = require('#azure/keyvault-secrets');
const MSRestAzure = require('ms-rest-azure');
const keyVaultName = process.env.KEY_VAULT_NAME;
const KVUri = `https://${keyVaultName}.vault.azure.net`;
const credential = new ClientSecretCredential(
process.env.AZURE_TENANT_ID,
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
);
const vault = new SecretClient(KVUri, credential);
function getCreds() {
return new Promise((res, rej) => {
MSRestAzure.loginWithServicePrincipalSecret(
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
process.env.AZURE_TENANT_ID,
(err, creds) => {
if (err) {
rej(err);
return;
}
res(creds);
},
);
});
}
const getResourceUrl = (resource, action) => `https://management.azure.com${resource}/${action}?api-version=2019-04-01`;
const resource = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Storage/storageAccounts/MyStore
const creds = await getCreds();
const client = new MSRestAzure.AzureServiceClient(creds);
const regenUrl = getResourceUrl(resource, 'regenerateKey');
await client.sendRequest({ method: 'POST', url: regenUrl }).then(console.log);
I'm getting an UnexpectedException response -
{
"error": {
"code": "UnexpectedException",
"message": "The server was unable to complete your request."
}
}
The Client ID/Secret belongs to an app registration that has access to the storage account, as well as Contributor and Storage Account Key Operator over that subscription.
I'm lead to think that I've not formed the request properly.
I am able to reproduce the error if I don't specify the request body.
Please provide the request body in the following format:
{
keyName: "key1 or key2 (basically which key you want to regenerate)"
}

login to [sap] cloud foundry and get api response using nodejs wrapper

I have a scenario, where I am trying to fetch the API responses of the following host url of my account spaces and orgs in cloudfoundry SAP.
https://api.cf.eu10.hana.ondemand.com
I am using nodejs (wrapper cf-client) script to authenticate but whenever I try to login it provides below error
Error: {"error":"unauthorized","error_description":"{"error":"invalid_grant","error_description":"User authentication failed: INVALID_AUTHORIZATION_HEADER_LENGTH"}"}
Here is my nodejs script
"use-strict";
const endpoint = "https://api.cf.eu10.hana.ondemand.com";
const username = "myusername"; //I have created a trial account
const password = "Password"; //I have created a trial account
const JsonFind = require('json-find');
const fs = require('fs')
const util = require('util');
const dJSON = require('dirty-json');
const CloudController = new (require("cf-client")).CloudController(endpoint);
const UsersUAA = new (require("cf-client")).UsersUAA;
const Apps = new (require("cf-client")).Apps(endpoint);
const Spaces = new (require("cf-client")).Spaces(endpoint);
const Orgs = new (require("cf-client")).Organizations(endpoint);
CloudController.getInfo().then( (result) => {
UsersUAA.setEndPoint(result.authorization_endpoint);
return UsersUAA.login(username, password);
}).then( (result) => {
Orgs.setToken(result);
return Orgs.getOrganizations();
}).then((result) => {
all_orgs = result.resources //returns api
get_orgs=util.inspect(all_orgs, {depth: null});
console.log(get_orgs)
});
What I have seen is when I normaly login with cf client it requires sso passcode along with username password.
how can i provide that here or any idea how can I login here and fetch the data.
Since I did little more research on this and found out that a token generator can be used to overcome this, below code.
"use-strict";
var totp = require('totp-generator');
const endpoint = "https://api.cf.eu10.hana.ondemand.com";
var token = totp('clientsecretidgoeshere');
const username = "myusername"; //I have created a trial account
const password = "Password"+token; //I have created a trial account
const JsonFind = require('json-find');
const fs = require('fs')
const util = require('util');
const dJSON = require('dirty-json');
const CloudController = new (require("cf-client")).CloudController(endpoint);
const UsersUAA = new (require("cf-client")).UsersUAA;
const Apps = new (require("cf-client")).Apps(endpoint);
const Spaces = new (require("cf-client")).Spaces(endpoint);
const Orgs = new (require("cf-client")).Organizations(endpoint);
CloudController.getInfo().then( (result) => {
UsersUAA.setEndPoint(result.authorization_endpoint);
return UsersUAA.login(username, password);
}).then( (result) => {
Orgs.setToken(result);
return Orgs.getOrganizations();
}).then((result) => {
all_orgs = result.resources //returns api
get_orgs=util.inspect(all_orgs, {depth: null});
console.log(get_orgs)
});

Resources