如何验证谷歌云功能以访问安全的应用引擎端点 [英] How to authenticate google cloud functions for access to secure app engine endpoints

本文介绍了如何验证谷歌云功能以访问安全的应用引擎端点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Google云平台推出了Identity Aware Proxy,以保护App Engine灵活的环境实例免受公共访问。

然而,这并不完全清楚这是否可以或应该



documentation (使用Python和Java示例)表示IAP身份验证工作流程,包括1)生成JWT令牌,2)创建OpenID令牌,3)然后将请求提交给Google App具有授权:持票人TOKEN 标头的引擎。



如果授权必须发生,这对于运行云功能似乎相当复杂每次调用一个函数。



Google云端功能还有另外一种方法可以访问受保护的GAE端点吗?

解决方案

如果您想从GCF拨打电话给受IAP保护的应用程序,您应该确实使用ID令牌。在Nodejs中没有例子,所以我使用 作为参考(风格可能是错误的,因为这是我第一次触摸nodejs)。与常规JWT声明集不同,它不应包含范围并具有target_audience。

  / ** 
*使IAP请求
*
* /
exports.CfToIAP = function CfToIAP(req,res){
var crypto = require('crypto'),
request = require('请求');
var token_URL =https://www.googleapis.com/oauth2/v4/token;
//服务帐号私钥(从service_account.json复制)
var key =----- BEGIN PRIVATE KEY ----- \\\
MIIEvQexsQ1DBNe12345GRwAZM = \\\
----- END PRIVATE KEY ----- \\\
;

// craft JWT
var JWT_header = new Buffer(JSON.stringify({alg:RS256,typ:JWT}))。toString('base64');
//准备索赔集
var iss =12345@12345.iam.gserviceaccount.com; //服务帐户电子邮件地址(从service_account.json复制)
var aud =https://www.googleapis.com/oauth2/v4/token;
var iat = Math.floor(new Date()。getTime()/ 1000);
var exp = iat + 120; //因为它没有被缓存,所以不需要长连接标记
var target_audience =12345.apps.googleusercontent.com; //这是可以通过点击3个点获得的IAP客户端ID - >在IAP配置页面中编辑OAuth客户端
var claims = {
iss:iss,
aud:aud,
iat:iat,
exp:exp,
target_audience:target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims))。toString('base64');
//连接头和声明集
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('。');
//获取id_token并使IAP请求
request.post({url:token_URL,form:{grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer',断言: ();}},function(err,res,body){
var data = JSON.parse(body);
var bearer = ['承载',data.id_token] .join('');
var options = {
url:'https://1234.appspot.com/',// IAP受保护的GAE应用程序
标题:{$ b $'User-Agent': 'cf2IAP',
'Authorization':bearer
}
};
request(options,function(err,res,body){
console.log('错误:',err);
});
});
res.send('done');
};
$ b / **
* package.json
*
* /

{
name:IAP- test,
version:0.0.1,
dependencies:{
request:> = 2.83
}
}

更新:建议不要捆绑服务帐户密钥,因此更好的选择是使用元数据服务器对于下面的示例工作,应启用Google身份和访问管理(IAM)API,并且App Engine默认服务帐户应具有服务帐户演员角色(默认编辑器不够):

  / ** 
*从CF向IAP后面的GAE应用发出请求:
* 1)从元数据服务器获取访问令牌。
* 2)准备JWT并使用IAM API projects.serviceAccounts.signBlob方法避免捆绑服务帐户密钥。
* 3)'交换'JWT for ID令牌。
* 4)使用ID令牌发出请求。
*
* /
exports.CfToIAP = function CfToIAP(req,res){
//进口和常量
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(''); // CF项目的应用程序默认服务帐户
const target_audience ='< IAP_client_ID>';
const IAP_GAE_app ='< IAP_protected_GAE_app_URL>';

//准备请求选项并使元数据服务器访问令牌请求
var meta_req_opts = {
url:['http://metadata.google.internal/computeMetadata/v1/实例/服务帐户/',
service_account,
'/ token']。join(''),
headers:{$ b $'User-Agent':user_agent,
'Metadata-Flavor':'Google'
}
};
request(meta_req_opts,function(err,res,body){
//从响应$ b $获取访问令牌var meta_resp_data = JSON.parse(body);
var access_token = meta_resp_data .access_token;

//准备{Base64url编码的标题} {Base64url编码的声明集}。{Base64url编码的签名}
// 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);
//准备声明集和base64编码它
var claims = {
iss:service_account,
aud:token_URL,
iat:iat,
exp:iat + 60,//因为没有缓存,所以不需要长寿命的令牌
target_audience:target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims))。toString('base64');

// conc atenate JWT头和声明集并获取签名usign IAM API projects.serviceAccounts.signBlob方法
var to_sign = [JWT_header,JWT_claimset] .join('。');
//使用IAM API签署JWT projects.serviceAccounts.signBlob方法
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,
'授权':['承载',access_token] .join('')
}
};
request(signature_req_opts,function(err,res,body){
//从响应获得签名并且形式为JWT
var JWT_signature = body.signature;
var JWT = [JWT_header ,JWT_claimset,JWT_signature] .join('。');

//获取ID令牌
request.post({url:token_URL,form:{grant_type:'urn:ietf:params :oauth:grant-type:jwt-bearer',assertion:JWT}},function(err,res,body){
//使用ID令牌向受IAP保护的GAE应用程序发出请求
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 ('')
}
};
请求(IAP_req_opts,function(err, res,body){
console.log('error:',err);
});
});
});
});
res.send('done');
};


Google Cloud Platform has introduced Identity Aware Proxy for protecting App Engine Flexible environment instances from public access.

However, it is not entirely clear if this can or should be used from Google Cloud Functions that are accessing GAE hosted API endpoints.

The documentation (with Python and Java examples) indicates an IAP authentication workflow consisting of 1) generating a JWT token, 2) creating an OpenID Token, 3) Then submitting requests to Google App Engine with an Authorization: Bearer TOKEN header.

This seems quite convoluted for running cloud functions if authorisation has to happen each time a function is called.

Is there another way for Google cloud functions to access secured GAE endpoints?

解决方案

If you want to make calls from GCF to IAP protected app, you should indeed be using ID tokens. There are no examples in Nodejs so I made one using this as a reference (style may be wrong since that's the first time I touch nodejs). Unlike regular JWT claims set, it should not contain scope and have 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 = "12345@12345.iam.gserviceaccount.com";  // 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"
  }
}

Update: Bundling service account key is not recommended, so a better option is to use the metadata server. For the below sample to work Google Identity and Access Management (IAM) API should be enabled and App Engine default service account should have Service Account Actor role (default Editor is not enough):

/**
 * 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');
};

这篇关于如何验证谷歌云功能以访问安全的应用引擎端点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆