英文:
Google Cloud Tasks - How to secure HTTP POST request?
问题
我一直在我的应用程序中使用Google Cloud Tasks,用于事件调度以在UI中响应事件状态更改后的事件。我发现在HTTP POST请求中存在问题,它并不是完全安全的。我在互联网上发现,只要有互联网连接和我的有效负载模式数据,任何人都可以请求这个HTTP POST请求,并可以操纵我的Firestore文档。
这是我的“task”对象,我在其中使用“onCreate”触发器来在队列中创建任务。
创建任务:
await tasksClient.createTask({ parent: queuePath, task });
那就是HTTP“onRequest”触发器,在scheduleTime到达时分派。
export const schedulerCallback = functions.https.onRequest(async (req, res) => {
我对此感到非常沮丧,找不到保护HTTP POST请求的解决方案。我来到这里在stackoverflow上,在这里之前有一些类似的问题,但与我的上下文无关。这与具体保护Google Cloud Tasks的HTTP POST请求无关。
经过我所有的努力和大量的研究,我终于找到了解决方案。我很高兴与你分享,看到答案。
英文:
I've been working with Google Cloud Tasks in one of my App, I used Google Cloud Tasks for events scheduling to respond in UI after event status changes if a particular scheduled time is reached and the task is dispatched.
And, I find out there was an issue with the HTTP POST request, it was not totally secured. I found on internet that, anyone with just internet connection and my payload pattern data can request to this HTTP POST request and can manipulate my document in firestore.
That was my task
object, the part of onCreate
trigger which I used to create task in the queue.
const task {
name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
httpRequest: {
httpMethod: ‘POST’,
url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}`,
body: Buffer.from(JSON.stringify(payload)).toString(‘base64’),
headers: { ‘Content-Type’: ‘application/json’ }
}
scheduleTime: {
seconds: expirationAtSeconds
}
}
To create the task:
await tasksClient.createTask({ parent: queuePath, task });
And that was the HTTP onRequest
Trigger, dispatched on scheduleTime arrives.
export const schedulerCallback = functions.https.onRequest(async (req, res) => {
// accessing the eventId that we sent over HTTP POST request as a query param.
const eventId = req.query.eventId as string;
try {
await admin.firestore().collection(“events”).doc(eventId).delete();
} catch(e) {
console.log(e);
res.status(500).send(e);
}
});
I was so frustrated of that, and wasn't found any solution for securing that HTTP POST request. I came here in stack overflow, here were some similar questions asked before but was out of my context. That was not related to specifically securing HTTP POST request with Google Cloud Tasks.
After putting all of my effort and so many research, I, finally find out the solution for that. And I'm happy to share it with you, See the answer.
答案1
得分: 2
以下是您要翻译的内容:
Securing an HTTP request is a solvable problem, so we don't need to worry about it.
Here's how you will do it:
First of all, before going through creating tasks and then to deal with them. You need a service account, with certain roles. And so using that service account authenticate the function invoker and allow only that service account to do all the stuff such as, Invoke Cloud Functions trigger, Create Service Account Tokens, and finally to enqueue tasks to the queue.
Having this in mind, Create a service account by going to IAM & Admin > Service Accounts. On the top menu you will see CREATE SERVICE ACCOUNT. Name your service account and go for CREATE AND CONTINUE. In the second step give the following roles to the service accounts (Cloud Function Invoker, Cloud Tasks Enqueuer, and Service Account Token Creator) and you are done with the service account.
Next, on IAM & Admin come to the first tab named IAM. Click Grant Access and in the first field “New principals” add your service account there and grant the above roles to it and save it.
Now, in your onCreate
Trigger add one additional field just like that:
const serviceAccountEmail = 'YOUR_SERVICE_ACCOUNT_HERE';
Next your task
object configuration is gonna look like this:
const task {
name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
httpRequest: {
httpMethod: 'POST',
url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}`,
body: Buffer.from(JSON.stringify(payload)).toString('base64'),
headers: { 'Content-Type': 'application/json' }
}
oidcToken: {
serviceAccountEmail,
audience: "https://${location}-${project}.cloudfunctions.net/schedulerCallback"
}
scheduleTime: {
seconds: expirationAtSeconds
}
}
After you have set up the service account and configured task with the oidcToken
, everytime, when onCreate
function triggers, the Google Cloud Tasks receive the instructions on who should make the HTTP request and where it should be sent.
The audience
field is specified same as the httpRequest
url (excluding query params). The main purpose of this is to verify that the token was intended for the target service (our cloud function in this case).
When the scheduled time arrives and the task is about to be dispatched, the service account specified serviceAccountEmail
field is used to generate an OpenID Connect (OIDC) token. This token claims about the identity of the service account, including the audience
claim that matches your Cloud Function’s URL.
The OIDC token is sent as part of the HTTP request’s Authorization header when the task is fully dispatched. When your Cloud Function receives the request, it can validate the token to ensure that the request is authenticated and authorized. It verifies the audience
claim matches its own URL, confirming that the token was intended for this service.
Next, create an asynchronous function to verify token:
async function verifyToken(token: string, location: any,project: any) {
const client = new OAuth2Client();
const ticket = await client.verifyIdToken({
idToken: token,
audience: `https://${location}-${project}.cloudfunctions.net/updateEventStatus`,
});
return ticket.getPayload();
}
The function above uses, OAuth2Client
so you need to install & import google-auth-library
.
// install library
$ npm install google-auth-library
// import library
import { OAuth2Client } from “google-auth-library”;
Now your schedulerCallback
is gonna look like this:
export const schedulerCallback = functions.https.onRequest(async (req, res) => {
// add the projectID and location
const project = json.parse(process.env.FIREBASE_CONFIG!).projectId;
const location = ‘us-central1’;
const eventId = req.query.eventId as string;
// access the headers > authorization of the request
const authorizationHeader = req.headers.authorization;
// check if authorization header is not null
if(!authorizationHeader) {
res.status(401).send(“unauthorized token”);
return;
}
// if authorizationHeader is not null access the token
const token = authorizationHeader.split(‘ ’)[1];
// verify ID token
try {
await verifyIdToken(token, location, project);
} catch (error) {
console.log(error);
res.status(401).send(“Unauthorized token”);
return;
}
// delete document
try {
await admin.firestore().collection(“events”).doc(eventId).delete();
} catch(e) {
console.log(e);
res.status(500).send(e);
}
});
With this done, now your HTTP POST request is secured, and no one outisde having just an internet connection can manipulate the document in your firestore. But only your app has the right to do it.
Learn more about Google Cloud Task scheduling by following step by step guide in my Medium blog post.
Enjoy!
英文:
Securing an HTTP request is a solvable problem, so we don't need to worry about it.
Here's how you will do it:
First of all, before going through creating tasks and then to deal with them. You need a service account, with certain roles. And so using that service account authenticate the function invoker and allow only that service account to do all the stuff such as, Invoke Cloud Functions trigger, Create Service Account Tokens, and finally to enqueue tasks to the queue.
Having this in mind, Create a service account by going to IAM & Admin > Service Accounts. On the top menu you will see CREATE SERVICE ACCOUNT. Name your service account and go for CREATE AND CONTINUE. In the second step give the following roles to the service accounts (Cloud Function Invoker, Cloud Tasks Enqueuer, and Service Account Token Creator) and you are done with the service account.
Next, on IAM & Admin come to the first tab named IAM. Click Grant Access and in the first field “New principals” add your service account there and grant the above roles to it and save it.
Now, in your onCreate
Trigger add one additional field just like that:
const serviceAccountEmail = 'YOUR_SERVICE_ACCOUNT_HERE';
Next your task
object configuration is gonna look like this:
const task {
name: `${taskName}/myTask-${snapshot.id}-${expireSeconds}`,
httpRequest: {
httpMethod: ‘POST’,
url: `https://${location}-${project}.cloudfunctions.net/schedulerCallback?eventId=${snapshot.id}`,
body: Buffer.from(JSON.stringify(payload)).toString(‘base64’),
headers: { ‘Content-Type’: ‘application/json’ }
}
oidcToken: {
serviceAccountEmail,
audience: “https://${location}-${project}.cloudfunctions.net/schedulerCallback”
}
scheduleTime: {
seconds: expirationAtSeconds
}
}
After you have set up the service account and configured task with the oidcToken
, everytime, when onCreate
function triggers, the Google Cloud Tasks receive the instructions on who should make the HTTP request and where it should be sent.
The audience
field is specified same as the httpRequest
url (excluding query params). The main purpose of this is to verify that the token was intended for the target service (our cloud function in this case).
When the scheduled time arrives and the task is about to be dispatched, the service account specified serviceAccountEmail
field is used to generate an OpenID Connect (OIDC) token. This token claims about the identity of the service account, including the audience
claim that matches your Cloud Function’s URL.
The OIDC token is sent as part of the HTTP request’s Authorization header when the task is fully dispatched. When your Cloud Function receives the request, it can validate the token to ensure that the request is authenticated and authorized. It verifies the audience
claim matches its own URL, confirming that the token was intended for this service.
Next, create an asynchronous function to verify token:
async function verifyToken(token: string, location: any,project: any) {
const client = new OAuth2Client();
const ticket = await client.verifyIdToken({
idToken: token,
audience: `https://${location}-${project}.cloudfunctions.net/updateEventStatus`,
});
return ticket.getPayload();
}
The function above uses, OAuth2Client
so you need to install & import google-auth-library
.
// install library
$ npm install google-auth-library
// import library
import { OAuth2Client } from “google-auth-library”;
Now your schedulerCallback
is gonna look like this:
export const schedulerCallback = functions.https.onRequest(async (req, res) => {
// add the projectID and location
const project = json.parse(process.env.FIREBASE_CONFIG!).projectId;
const location = ‘us-central1’;
const eventId = req.query.eventId as string;
// access the headers > authorization of the request
const authorizationHeader = req.headers.authorization;
// check if authorization header is not null
if(!authorizationHeader) {
res.status(401).send(“unauthorized token”);
return;
}
// if authorizationHeader is not null access the token
const token = authorizationHeader.split(‘ ’)[1];
// verify ID token
try {
await verifyIdToken(token, location, project);
} catch (error) {
console.log(error);
res.status(401).send(“Unauthorized token”);
return;
}
// delete document
try {
await admin.firestore().collection(“events”).doc(eventId).delete();
} catch(e) {
console.log(e);
res.status(500).send(e);
}
});
With this done, now your HTTP POST request is secured, and no one outisde having just an internet connection can manipulate the document in your firestore. But only your app has the right to do it.
Learn more about Google Cloud Task scheduling by following step by step guide in my Medium blog post.
Enjoy!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论