GCP上IAP下的程序性认证

概述

我们最近开始将大量的基础设施转移到谷歌云平台上。为此,我们还决定将很多不应该从组织外部访问的实体转移到GCP的身份识别代理后面。谷歌的身份感知代理(IAP)实现了对GCP资源的零信任访问。它允许对应用程序和资源执行访问控制策略,可以配置基于组的应用程序访问和基于服务账户的访问,而不需要使用任何VPN。任何存在于IAP后面的资源或应用程序只能由拥有正确身份和访问管理(IAM)角色的成员通过代理访问。

问题所在

我们的很多自动化框架都是在我们的测试应用程序上运行的,但现在把它们移到IAP后面,对处理IAP认证提出了新的挑战。通过阅读GCP上提供的文档,我们发现基于服务账户的应用程序的程序化认证过程是非常麻烦的。首先,我们必须把服务账户添加到访问列表中。要做到这一点,请进入身份识别代理页面,然后选择要保护的资源。然后在信息面板上,你可以添加服务账户的电子邮件地址,以及所需的访问策略。这就给你一个服务账户的JSON文件。类似这样的文件:

{
 "type": "service_account",
 "project_id": "PROJECT_ID",
 "private_key_id": "abcd123",
 "private_key": "-----BEGIN PRIVATE KEY-----qwerty6789-----END PRIVATE KEY-----\n",
 "client_email": "someone@PROJECT_ID.iam.gserviceaccount.com",
 "client_id": "1239876543210",
 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
 "token_uri": "https://oauth2.googleapis.com/token",
 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
 "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/someone%40PROJECT_ID.iam.gserviceaccount.com"
}
复制代码

然后使用你在上一步中得到的客户ID的target_audience ,生成一个基于JWT的令牌。你也可以通过进入IAP页面获得客户端ID,选择正在考虑的资源,点击省略号,然后点击编辑OAuth客户端。现在,请注意,生成JSON网络令牌(JWT)本身是一项单独的任务,为此你需要一些JWT库。然后,JWT本身需要使用服务账户JSON文件中的私钥,用RSA-256签名。然后,签名字节将被添加到令牌中,用点分隔符,如:

{
 "alg": "RS256",
 "typ": "JWT",
 "kid": "PRIVATE_KEY_ID"
}
.
{
 "iss": "someone@PROJECT_ID.iam.gserviceaccount.com",
 "sub": "someone@PROJECT_ID.iam.gserviceaccount.com",
 "aud": "https://SERVICE_NAME/",
 "iat": 1600948084,
 "exp": 1600951684
}
.
{
   [signature bytes]
}
复制代码

签名的JWT以及签名字节的附录和base64url编码看起来是这样的:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92NC90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.UFUt59SUM2_AW4cRU8Y0BYVQsNTo4n7AFsNrqOpYiICDu37vVt-tw38UKzjmUKtcRsLLjrR3gFW3dNDMx_pL9DVjgVHDdYirtrCekUHOYoa1CMR66nxep5q5cBQ4y4u2kIgSvChCTc9pmLLNoIem-ruCecAJYgI9Ks7pTnW1gkOKs0x3YpiLpzplVHAkkHztaXiJdtpBcY1OXyo6jTQCa3Lk2Q3va1dPkh_d--GU2M5flgd8xNBPYw4vxyt0mP59XZlHMpztZt0soSgObf7G3GXArreF_6tpbFsS3z2t5zkEiHuWJXpzcYr5zWTRPDEHsejeBSG8EgpLDce2380ROQ
复制代码

一旦你有了签名的JWT,你必须对它进行base64url编码,然后提出一个OIDC访问令牌请求。这个请求将是一个POST请求,应该是向Google OAuth API URL发出的。两个参数,grant_typeassertion 将被添加到这个请求中。grant_typeurn:ietf:params:oauth:grant-type:jwt-bearer 的字符串值,而assertion 参数有签名的 JWT,包括签名字节,作为它的值:

POST /token HTTP/1.1
Host: GOOGLE_OAUTH_API_URL
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.ixOUGehweEVX_UKXv5BbbwVEdcz6AYS-6uQV6fGorGKrHf3LIJnyREw9evE-gs2bmMaQI5_UbabvI4k-mQE4kBqtmSpTzxYBL1TCd7Kv5nTZoUC1CmwmWCFqT9RE6D7XSgPUh_jF1qskLa2w0rxMSjwruNKbysgRNctZPln7cqQ
复制代码

收到的响应将包含承载器访问令牌。然后,这个令牌需要被纳入所有对IAP后面的资源提出的请求中。

在开始的时候,如果你读了上面的描述,如果这个描述还不够令人生畏,那么就读一下文档本身;这些步骤似乎很混乱,似乎可以做一些修改,使之更容易理解。

解决方案

幸运的是,谷歌已经制作了google auth库,所有上述程序都已经实现,并被很好地抽象出来。搜索google-auth-library会返回多种语言的实现结果。 我们使用node库,因为我们的自动化项目主要是通过protractor和javascript实现的。 实现看起来像这样:

function main(targetAudience = 'CLIENT_ID_GOES_HERE') {
  const { GoogleAuth } = require('google-auth-library');
  const auth = new GoogleAuth();

  function request() {
    auth.getIdTokenClient(targetAudience).then(function (client) {
      client.getRequestHeaders().then(function (headers) {
        console.info('headers', headers);
      });
    });
  }
  request();
}

main();
复制代码

这被用来生成Bearerauth token,然后可以进一步用于在自动化测试中向我们的测试应用程序发出请求。

在应用程序自动化中,我们需要Protractor的浏览器实例来运行测试应用程序的自动化,我们利用browsermob-proxy的节点实现和browsermob-proxy服务器。使用Protractor的生命周期方法beforeAll ,我们确保在执行任何规格文件之前获取令牌并准备使用:

exports.setIap = function () {
  beforeAll(function (done) {
    global.iapAuthToken = 'CLIENT_ID_GOES_HERE';
    if (browser.params.isRunOnTestApp) {
      var targetAudience = '';
      const auth = new GoogleAuth();
      auth.getIdTokenClient(targetAudience).then(function (client) {
        client.getRequestHeaders().then(function (headers) {
          iapAuthToken = headers.Authorization;
          done();
        });
      });
    } else {
      done();
    }
  });
};
复制代码

然后,这个令牌的全局可用值被用来在生成的代理中设置授权头。 我们为此使用了addHeader 方法:

var headersToSet = {};
headersToSet.Authorization = global.iapAuthToken;
proxy.addHeader(port, headersToSet, function (err, resp) {
  if (err) {
    console.error('Error encountered', err);
  } else {
    console.info(`Headers added ${JSON.stringify(headersToSet)}, ${resp}`);
  }
});
复制代码

这使得我们能够在通过Protractor浏览器实例发出的每个请求中添加授权承载令牌。

在protractor浏览器功能中添加代理选项是为了让Protractor通过指定的host: port 组合产生的代理来处理所有的浏览器请求:

proxy: {
    "proxyType": "manual",
    "httpProxy": `${this.params.proxyHost}:${this.params.proxyPort}`,
    "sslProxy": `${this.params.proxyHost}:${this.params.proxyPort}`
}
复制代码

在我们基于Node.js的自动化中,我们使用请求模块来为他们的断言进行API调用。我们重写了它,以适应在每个请求中添加授权承载令牌:

exports.customRequest = function (options, callback) {
  var base;
  if (browser.params.isRunOnTestApp) {
    base = request.defaults({
      headers: {
        Authorization: iapAuthToken,
      },
      jar: true,
    });
  } else {
    base = request.defaults({
      jar: true,
    });
  }
  base(options, callback);
};
复制代码

对于我们所有通过JMeter运行的负载测试,我们使用了上面第一个例子中提到的类似脚本。它生成访问令牌并将其写入一个文件中。考虑到这些令牌的有效期很短,我们在运行负载测试时生成令牌并将其写入文件。

后果

IAP是一个很好的选择,可以调节对私有资源的访问,而不需要实现VPN隧道连接。但是,将自动代理的可访问性扔在一起,在这里创造了新的有趣的挑战。它有一些明显的优点:

  1. 方便对资源的访问监管
  2. 通过用户和服务的IAM角色对访问进行细粒度的控制
  3. 通过不受信任的网络访问,不需要VPN隧道的要求

但在实施程序化认证的过程中,我们也发现了一些缺点:

  1. 在私有资源上的执行速度较慢,这是为了增加授权头而重新路由访问的结果。
  2. 在自动化测试中处理认证的额外开销,这不符合被测试的用户流。用户不会通过IAP访问实际的应用程序。

猜你喜欢

转载自juejin.im/post/7127672400585424926
gcp
IAP
今日推荐