Si vous souhaitez passer des appels de GCF à une application protégée par IAP, vous devez en effet utiliser des jetons d'identification. Il n'y a pas d'exemple dans Nodejs donc j'en ai fait un en utilisant this comme référence (le style peut être faux puisque c'est la première fois que je touche nodejs). Contrairement aux jeux de revendications JWT standard, il ne doit pas contenir de portée et avoir l'attribut target_audience.
/**
* Make IAP request
*
*/
exports.CfToIAP = function CfToIAP (req, res) {
var crypto = require('crypto'),
request = require('request');
var token_URL = "https://www.googleapis.com/oauth2/v4/token";
// service account private key (copied from service_account.json)
var key = "-----BEGIN PRIVATE KEY-----\nMIIEvQexsQ1DBNe12345GRwAZM=\n-----END PRIVATE KEY-----\n";
// craft JWT
var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64');
// prepare claims set
var iss = "[email protected]"; // service account email address (copied from service_account.json)
var aud = "https://www.googleapis.com/oauth2/v4/token";
var iat = Math.floor(new Date().getTime()/1000);
var exp = iat + 120; // no need for a long linved token since it's not cached
var target_audience = "12345.apps.googleusercontent.com"; // this is the IAP client ID that can be obtained by clicking 3 dots -> Edit OAuth Client in IAP configuration page
var claims = {
iss: iss,
aud: aud,
iat: iat,
exp: exp,
target_audience: target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64');
// concatenate header and claimset
var unsignedJWT = [JWT_header, JWT_claimset].join('.');
// sign JWT
var JWT_signature = crypto.createSign('RSA-SHA256').update(unsignedJWT).sign(key, 'base64');
var signedJWT = [unsignedJWT, JWT_signature].join('.');
// get id_token and make IAP request
request.post({url:token_URL, form: {grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion:signedJWT}}, function(err,res,body){
var data = JSON.parse(body);
var bearer = ['Bearer', data.id_token].join(' ');
var options = {
url: 'https://1234.appspot.com/', // IAP protected GAE app
headers: {
'User-Agent': 'cf2IAP',
'Authorization': bearer
}
};
request(options, function (err, res, body) {
console.log('error:', err);
});
});
res.send('done');
};
/**
* package.json
*
*/
{
"name": "IAP-test",
"version": "0.0.1",
"dependencies": {
"request": ">=2.83"
}
}
Mise à jour: clé de compte de service de bottelage est pas recommandé, donc une meilleure option est d'utiliser le serveur de métadonnées. Pour l'exemple ci-dessous pour travailler API Google Identity and Access Management (IAM) doit être activé et App Engine compte de service par défaut devrait avoir le rôle du compte de service Acteur (éditeur par défaut ne suffit pas):
/**
* Make request from CF to a GAE app behind IAP:
* 1) get access token from the metadata server.
* 2) prepare JWT and use IAM APIs projects.serviceAccounts.signBlob method to avoid bundling service account key.
* 3) 'exchange' JWT for ID token.
* 4) make request with ID token.
*
*/
exports.CfToIAP = function CfToIAP (req, res) {
// imports and constants
const request = require('request');
const user_agent = '<user_agent_to_identify_your_CF_call>';
const token_URL = "https://www.googleapis.com/oauth2/v4/token";
const project_id = '<project_ID_where_CF_is_deployed>';
const service_account = [project_id,
'@appspot.gserviceaccount.com'].join(''); // app default service account for CF project
const target_audience = '<IAP_client_ID>';
const IAP_GAE_app = '<IAP_protected_GAE_app_URL>';
// prepare request options and make metadata server access token request
var meta_req_opts = {
url: ['http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/',
service_account,
'/token'].join(''),
headers: {
'User-Agent': user_agent,
'Metadata-Flavor': 'Google'
}
};
request(meta_req_opts, function (err, res, body) {
// get access token from response
var meta_resp_data = JSON.parse(body);
var access_token = meta_resp_data.access_token;
// prepare JWT that is {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount for more info
var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64');
var iat = Math.floor(new Date().getTime()/1000);
// prepare claims set and base64 encode it
var claims = {
iss: service_account,
aud: token_URL,
iat: iat,
exp: iat + 60, // no need for a long lived token since it's not cached
target_audience: target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64');
// concatenate JWT header and claims set and get signature usign IAM APIs projects.serviceAccounts.signBlob method
var to_sign = [JWT_header, JWT_claimset].join('.');
// sign JWT using IAM APIs projects.serviceAccounts.signBlob method
var signature_req_opts = {
url: ['https://iam.googleapis.com/v1/projects/',
project_id,
'/serviceAccounts/',
service_account,
':signBlob'].join(''),
method: "POST",
json: {
"bytesToSign": new Buffer(to_sign).toString('base64')
},
headers: {
'User-Agent': user_agent,
'Authorization': ['Bearer', access_token].join(' ')
}
};
request(signature_req_opts, function (err, res, body) {
// get signature from response and form JWT
var JWT_signature = body.signature;
var JWT = [JWT_header, JWT_claimset, JWT_signature].join('.');
// obtain ID token
request.post({url:token_URL, form: {grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion:JWT}}, function(err, res, body){
// use ID token to make a request to the IAP protected GAE app
var ID_token_resp_data = JSON.parse(body);
var ID_token = ID_token_resp_data.id_token;
var IAP_req_opts = {
url: IAP_GAE_app,
headers: {
'User-Agent': user_agent,
'Authorization': ['Bearer', ID_token].join(' ')
}
};
request(IAP_req_opts, function (err, res, body) {
console.log('error:', err);
});
});
});
});
res.send('done');
};
Savez-vous s'il existe un risque de perte de performances pour l'authentification de chaque demande de fonction de cloud? – shongololo
En tant que couche de protection supplémentaire, il aurait un impact de latence de bout en bout faible, mais l'authentification ne se produit pas au niveau de l'instance, donc il ne devrait pas y avoir d'impact sur le temps CPU. –
En fait, ce n'est pas une bonne approche, car il faut regrouper la clé privée qui n'est pas recommandée. Une meilleure approche consiste à utiliser le serveur de métadonnées. Je mettrai à jour l'échantillon plus tard. –