Google Cloud function accessing a secret from Secrets Manger - node.js

I'm having issues getting a cloud function to access a secret from the secrets manager. Basically I want to have my front-end access secrets by sending a request to the backend and then the backend getting the secret from secrets manager.
My cloud function endpoint looks like this:
Endpoint.get("/get-key", authMiddleware, async (req: any, res: Response) => {
try {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
});
const project = req.params.projectId;
const secret = req.params.secret;
const name = `projects/${project}/secrets/${secret}/versions/latest`;
const credentials = await auth.getCredentials();
const ver = new SecretManagerServiceClient({credentials,projectId:project});
const request = {
name,
};
const response = await ver.accessSecretVersion(request);
const payload = response.payload.data.toString();
console.log(`Payload: ${payload}`);
return res.status(200).send({ payload });
} catch (error:any) {
console.log("get key error: ", error);
return res.status(500).send(error.message);
}
});
When I try to access that endpoint I get this error:
PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
I tried explicitly setting the projectId there and it still gives me that error. Not sure what else I can change. perhaps the "latest" is not a valid endpoint for the secrets manager.

PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
The above error occurs when you exceed usage quota as discussed in this github thread or due to ToS violations. You may need to submit the appeal. You can contact Google support for the same.
Also make sure you have given secretAccessor role to your service account.

Related

Required claim nbf not present in token (using Firebase Microsoft Signin trying to access MicrosoftGraph)

I currently have an app with the following structure: Angular front-end, Node.js server.
We have implemented Google Cloud's Identity Providers to sign in using Google and/or Microsoft.
Google Sign-in and access the Google Cloud Admin SDK working perfectly, however trying to access Microsoft Graph is giving the following error:
UnhandledPromiseRejectionWarning: Error: Required claim nbf not present in token
According to Firebase documentation you can use the access token recieved from the Signin to access Graph:
Firebase documentation screenshot
On successful completion, the OAuth access token associated with the provider can be retrieved from the firebase.auth.UserCredential object returned. Using the OAuth access token, you can call the Microsoft Graph API. For example, to get the basic profile information, the following REST API can be called:
curl -i -H "Authorization: Bearer ACCESS_TOKEN" https://graph.microsoft.com/v1.0/me
When running the above in url I get the same error:
{"error":{"code":"InvalidAuthenticationToken","message":"Required claim nbf not present in token","innerError":{"date":"2022-05-26T12:51:11","request-id":"##########","client-request-id":"##########"}}}
I'm using the signInWithPopup in my authentication service (authentication.service.ts):
await signInWithPopup(this.auth, provider)
.then((result) => {
const credential = OAuthProvider.credentialFromResult(result);
const accessToken = credential.accessToken;
const idToken = credential.idToken;
this.setCurrentUser(result.user);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
const email = error.email;
const credential = OAuthProvider.credentialFromError(error);
console.log(error);
});
I send the accessToken to my server (app.ts):
app.get(
'/api/microsoft-get-organisation',
async (req: express.Request, res: express.Response) => {
//https://graph.microsoft.com/v1.0/organization
const organisation = await ms.getOrganisation(req.headers, '/organization');
res.send(JSON.stringify(organisation));
}
);
export const getOrganisation = async (headers: any, graphEndpoint: string) => {
const client = await getAuthenticatedClient(headers);
return await client.api(graphEndpoint).get();
};
async function getAuthenticatedClient(headers: any) {
const client = await graph.Client.init({
authProvider: (done: any) => {
done(null, headers.authorization.split(' ')[1]);
},
});
return client;
}
When verifying the token I can see that there is no nbf claim:
token screen shot
Any advice on what I have done wrong so that I can access Microsoft Graph?

PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' Node js

I have created a webhook for WhatsApp Chatbot using NodeJS following this online article: https://dev.to/newtonmunene_yg/creating-a-whatsapp-chatbot-using-node-js-dialogflow-and-twilio-31km
The webhook is linked to Twilio Sandbox for WhatsApp.
I have also provided the DialogFlow Admin API permission to service account on Google Cloud Platform.
When I send a new message from WhatsApp, it's received on Twilio and the webhook is triggered, but I am getting the following error on the console on my local machine.
"Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/xxxx-xxx-xxxx/agent' denied."
I am using Ngrok to tunnel the localhost build to the web and using that URL as the webhook URL in Twilio.
We have a client demo for this feature, so any quick help is appreciated. I am placing my dialog flow code and controller code below
dialogflow.ts
const dialogflow = require("dialogflow");
const credentials = require("../../credential-new.json");
const sessionClient = new dialogflow.SessionsClient({
credentials: credentials
});
const projectId: string = process.env.DIALOGFLOW_PROJECT_ID!;
export const runQuery = (query: string, number: string) => {
return new Promise(async (resolve, reject) => {
try {
// A unique identifier for the given session
//const sessionId = uuid.v4();
const sessionId = number;
// Create a new session
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: query,
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
const result = responses[0].queryResult;
resolve(result);
} catch (error) {
reject(error);
}
});
};
This issue got resolved by creating a new account on DialogFlow and providing new API key.
I think the problem is with the service account. Make sure you use the same email which is registered with Dialogflow and GCP and then create a service account and also make sure the service account email is present in the credential-new.json file is the same service account which has the Dialog Flow Admin Role and also check you have given the valid path to access credential-new.json file in the code.
You can safely do this by going to the settings menu on Dialogflow and then clicking on the project id, it will take you to the correct place.
Also, there may be a possibility that you forget to enable the Dialogflow API from the API section on GCP.

Why is Azure KeyVault getSecret returning Promise <pending>?

I followed the Microsoft tutorial on this link: https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-node but I want to have them as separate functions instead of putting them into one main function.
This is what I have, setSecret works fine but getSecret is returning Promise ?
Both getSecret and setSecret return a Promise because they are asynchronous methods that need to make an HTTP request to the Key Vault service.
If you were to try the following:
const secretPromise = client.setSecret(secretName, secretValue);
You'll notice that secretPromise is a Promise<KeyVaultSecret> as per the API documentation
This allows you to wait for the secret to be set and get back the newly set secret:
const secret = await client.setSecret(secretName, secretValue);
Be mindful that by not waiting for the setSecret call to succeed you will be unable to:
Get the newly created secret (unless you get lucky with timing)
Be notified and handle any errors if setSecret fails (you can verify this by creating the secret client with an invalid Key Vault URL and running both functions - azureSetSecret will claim success but azureGetSecret will throw an error)
I'm not sure if you are trying to find a way which allows you to obtain key vault secrets in nodejs. As the quick start sample you mentioned above, microsoft provides a async method await client.getSecret(secretName) for nodejs.
Here I'd like to recommend you using rest api to access key vault secret. When calling the api, you need to generate an access token as the request header, you can refer to this sample or take a look at my test project below.
calling api:
const axios = require('axios');
const auth = require('./credenticalflow');
let vaultName = 'key vault name';
let keyName = 'key name';
let accesstoken = '';
let secret = '';
init();
async function init(){
const authResponse = await auth.getToken(auth.tokenRequest);
accesstoken = authResponse.accessToken;
getsecret(vaultName,keyName,accesstoken);
console.log("22222222:"+secret);
}
function getsecret(vaultName,keyName,token){
console.log('the token is :'+ token);
axios.get('https://'+vaultName+'.vault.azure.net/secrets/'+keyName+'/7d4b682f5c9a41578602aa5b86611aa7?api-version=7.1',{
headers: {
'Authorization': 'Bearer '+token
}
})
.then(function (response) {
// handle success
secret = response.data.value;
console.log("1111111:"+secret);
})
.catch(function (error) {
// handle error
console.log('error');
});
}
generate access token:
const msal = require('#azure/msal-node');
const msalConfig = {
auth: {
clientId: 'azure ad app cilent id',
authority: 'https://login.microsoftonline.com/<your tenant name such as xx.onmicrosoft.com>',
clientSecret: 'client secret for the azure ad app',
}
};
const tokenRequest = {
scopes: ['https://vault.azure.net/.default'],
};
const cca = new msal.ConfidentialClientApplication(msalConfig);
async function getToken(tokenRequest) {
return await cca.acquireTokenByClientCredential(tokenRequest);
}
module.exports = {
tokenRequest: tokenRequest,
getToken: getToken
};

Google Directory API: Unable to access User/Group endpoints using Service Account (403)

I am trying to verify members of groups using the Google Directory API and cannot get past a 403 error every time I make the request.
I am using a service account, which I have enabled the "Enable G Suite Domain-wide Delegation" option for. I have also added the "https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group" Scopes using the Client ID within Suite under, "Manage API Client Access"
Code wise, I am using Node for this, and the google supplied googleapis package from NPM.
The external JSON file is the JSON credentials file downloaded when I created the service user.
Here's the code of me trying to get the request.
import { google } from 'googleapis';
async function getGroupUsers(){
const auth = await google.auth.getClient({
keyFile: './src/jwt.keys.json',
scopes: [
'https://www.googleapis.com/auth/admin.directory.group',
'https://www.googleapis.com/auth/admin.directory.group.member',
],
});
const admin = google.admin({
version: 'directory_v1',
auth,
});
const res = await admin.groups.get({
groupKey: 'redacted#domain.redacted',
});
console.log(res)
}
I can't see any obvious reason this isn't working, as I can't see how the user doesn't have permission to the resource?
Obviously missing something obvious here, as the google documentation for this is all over the shop sadly.
Help greatly appreciated!
Thanks
Gareth
Ok after much banging of head and googling I finally for there with this, final working code is as follows, not the inclusion of the client.subject value, which has to be an administrator for the domain in question.
async function validateToken(idToken) {
const keys = JSON.parse(GOOGLE_CREDS);
const client = auth.fromJSON(keys);
client.scopes = [
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group',
];
client.subject = 'admin#gsuite.domain';
const admin = google.admin({
version: 'directory_v1',
// auth,
auth: client,
});
const res = await admin.groups.list({
domain: 'redacted',
userKey: email,
});
const { groups } = res.data;
let role = '';
// Check for user role
if (containsGroup(USER_GROUP, groups)) {
role = USER_GROUP;
}
// Check for admin role
if (containsGroup(ADMIN_GROUP, groups)) {
role = ADMIN_GROUP;
}
// Not an admin or user so return unathenticated
if (role === '') {
return authResponse();
}
return successResponse({
'X-Hasura-User-Id': userid,
'X-Hasura-Email': email,
'X-Hasura-Role': role,
'X-Hasura-Groups': groups.map(group => group.id),
'Cache-Control': 'max-age=600',
});
}

Google cloud client libraries and user authentication

I am developing my first app for Google Cloud Platform.
In particular, I am using Node.js as base-framework. Google itself provides Node.js client libraries to interact with their services.
For instance, this code is able to create a new bucket within Cloud Storage:
var storage = require('#google-cloud/storage')();
var bucket = storage.bucket('albums');
bucket.create(function(err, bucket, apiResponse) {
if (!err) {
// The bucket was created successfully.
}
});
//-
// If the callback is omitted, we'll return a Promise.
//-
bucket.create().then(function(data) {
var bucket = data[0];
var apiResponse = data[1];
});
However, if I deploy this code on Google Application Engine, the action above is done using a service account (I suppose, at least) and not as end-user, even after an OAuth authentication, thus ignoring the IAM policies in place.
How could I avoid this problem, and use an user-centric flow for my requested? Can I use the Identiy-Aware Proxy? If yes, how?
Update
To make my question more understandable:
Consider this code:
router.get('/test2', oauth2.required, (req, res, next) => {
const Storage = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
// Lists all buckets in the current project
storage
.getBuckets()
.then(results => {
const buckets = results[0];
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
res.send("There are " + buckets.length + " buckets.");
})
.catch(err => {
res.send(err);
});
});
This route can be invoked if a given user has already signed in via OAuth2.
I would like to invoke the getBuckets() method passing the OAuth accessToken to perform this operation impersonating the user itself.
In this way, the action cannot skip the IAM rules in place in GCP for that given user currently logged.
I didi try:
const storage = new Storage({
auth: {
'bearer': req.user.accessToken
}});
But it does not work. The application still uses my default account.
You have two options to make sure your requests are allowed:
Grant the necessary permissions on the bucket and/or objects to your service account. This works if you control the data, but not if your application has to function with buckets/objects the user controls.
Do the "three legged Oauth" flow to get permission to make calls on behalf of the user. Unfortunately the client library you are calling doesn't support this. I don't know where you got the auth:{'bearer':...} from, but even if that did work the token you are passing wouldn't have the required scopes to access the bucket.
This autogenerated library does support three-legged auth. You'd use it soemthing like:
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
function handle_requests_to_redirect_url() {
// extract code query string parameter
token = await oauth2Client.getToken(code);
// Save token
}
if (no_known_auth_token_for_user()) {
// redirect user to
oauth2Client.generateAuthUrl({access_type:'offline', scope: ['https://www.googleapis.com/auth/devstorage.read_write']});
// after clicking OK, user is redirected to YOUR_REDIRECT_URL
}
var token = get_saved_token_for_this_user();
oauth2Client.setCredentials(token);
var storage = google.storage({version: 'v1', auth: oauth2Client});
storage.buckets.list(function (e,res) {
console.log(res);
});

Resources