Index
概要
这篇文章的初衷主要来自客户的一个问题:
作为贸易公司,每天有大量的出货入货单据存储在kintone上。但是渐渐的容量不够用了,单单购买kintone容量也无法满足需求。
现在的想法是有什么其他的办法能起到扩容的效果?前提条件是:这些单据算得上是公司的商业机密,一定要保证安全性。
为此,我们做了以下解决方案的demo插件。
这次使用的是阿里云对象存储(Object Storage Service,简称 OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。
在本文中,我们会将kintone的附件存储到OSS中,并通过STS授权来访问。这样在增加存储容量之外,还能避免泄露机密信息,安全又可靠。
创建应用
代码基于 使用“Tencent Cos for Kintone” 插件实现腾讯云+kintone文件管理 修改而来。
请参考或重用该插件的表单设置,创建一个kintone应用。
阿里云设置
OSS
开通OSS后我们可以通过“控制台”进入对象存储OSS设置画面。
点击"创建Bucket"后填写Bucket名称、并选择离你所在地近的地域。(请记住Bucket名称、地域)
由于代码中会使用OSS的SDK去访问阿里云API,因此我们需要开启Bucket的跨域设置。
在“历史访问路径”中找到我们创建的Bucket,在“数据安全”中找到“跨域设置”,创建一条跨域规则。
为求简单,我们的来源和headers都设置为*。
在Method中,由于我们涉及到列表、上传、更新、删除等操作,因此勾选对应的GET、POST、PUT、DELETE即可。
STS
此时,如果使用我们主账户的AccessKeyId和AccessKeySecret,OSS SDK就可以直接通过API来操作该Bucket。
然而,这种操作会产生主账户信息泄露的风险。
一旦主账户信息泄露,对方就可以利用API用来使用其他阿里云的服务,而因此产生的费用则由主账户的所有者来承担。
为了解决这个问题,阿里云官方提供了 STS临时访问凭证方案。
我们可以利用 “访问控制RAM”(Resource Access Management)来创建一个角色。
通过STS(Security Token Service)来为该角色临时授于OSS的操作权。
参考官方方案,我们需要创建一个仅仅只有“OpenAPI 调用访问”权限的用户。
点击复制,将该用户的AccessKey ID及AccessKey Secret保存到本地任意文件中。(将被使用在插件设置中)
请务必保存以上信息, Secret值只显示一次。如您遗失这个 AccessKey,可以创建新的来替代。
为刚刚创建的RAM用户授予AliyunSTSAssumeRoleAccess的系统策略。
创建一个OSS专用的角色,用于获取临时访问凭证。
建立一个自定义的权限策略,并赋予其操作我们之前创建的Bucket的API操作权限。
我们需要在Action下添加Object的Get、Delete、Put、List权限。
在Resource下填写自己的AccountID和Bucket名(例如本次创建的“kintone-oss-demo”)。
为了方便大家,这里提供文档模版,请修改对应的AccountID和Bucket名。
{ "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "oss:GetObject", "oss:DeleteObject", "oss:PutObject", "oss:ListObjects" ], "Resource": [ "acs:oss:*:your account id:your bucket", "acs:oss:*:your account id:your bucket/*" ] } ] }
填写策略名称后保存。
回到角色画面,找到我们之前的创建的角色。
为其添加我们之前定义的策略。
点击角色名,进入详细画面。
复制ARN,并保存到本地文件。(将被使用于插件配置中)
插件准备
到这里,OSS和STS的设置完成了。下面就需要到kintone环境里设置插件。
Demo插件源码
可以通过下面的地址获取源码。
请按Readme的说明进行编译和安装。
插件配置
插件配置 | OSS信息 |
---|---|
OSS 身份识别ID | AccessKey ID |
OSS 身份密钥 | AccessKey Secret |
OSS 存储桶名 | Bucket名 |
OSS 地域信息 | bucket中设置的地域的Region ID。可以参照 这里 获取对应信息 |
Role | ARN |
验证
配置完插件设置后,可以添加一条记录并上传文件。
在阿里云的“对象存储OSS”新建立的路径文件中就已上传了刚刚那个文件。
注意事项
由于暴露的AccessKey ID及AccessKey Secret是新建用户的,与主账户无关。
而且该用户只能通过STS扮演、拥有部分API权限的角色,才能正常访问某个Bucket。
因此由信息泄露造成的风险被降到了最低。
关键代码
最后聊一下核心代码 sts.js 及调用方案 provide.js
this.oss = new OSS({ region: config.region, bucket: config.bucket, accessKeyId: res.Credentials.AccessKeyId, accessKeySecret: res.Credentials.AccessKeySecret, stsToken: res.Credentials.SecurityToken, refreshSTSToken: async () => { const {Credentials} = await this.sts.assumeRole(config.role); return { accessKeyId: Credentials.AccessKeyId, accessKeySecret: Credentials.AccessKeySecret, stsToken: Credentials.SecurityToken, }; }, refreshSTSTokenInterval: 3600, });
从调用处provide.js可以看到,OSS 会通过refreshSTSToken去实现Token的刷新。
而刷新的时间间隔是refreshSTSTokenInterval中定义的3600秒,即1小时。
async assumeRole(role, policy, expiration) { const method = 'POST'; const opts = this.options; const params = { Action: 'AssumeRole', RoleArn: role, RoleSessionName: 'app', DurationSeconds: expiration || 3600, Format: opts.format, Version: opts.apiVersion, AccessKeyId: opts.accessKeyId, SignatureMethod: opts.sigMethod, SignatureVersion: opts.sigVersion, SignatureNonce: Math.random(), Timestamp: new Date().toISOString(), }; if (policy) { params.Policy = typeof policy === 'string' ? JSON.stringify(JSON.parse(policy)) : JSON.stringify(policy); } params.Signature = STS.getSignature(method, params, opts.accessKeySecret); const result = await kintone.proxy( opts.endpoint, method, {'Content-Type': 'application/x-www-form-urlencoded'}, new URLSearchParams(params).toString(), ); const body = JSON.parse(result[0]); const status = result[1]; if (status !== 200) { const err = new Error(); err.status = status; const {Code, Message, RequestId} = body || {}; err.message = `${Code}: ${Message}`; err.requestId = RequestId; throw err; } return body; }
在sts.js中的token获取方案,是向sts.aliyuncs.com 发送对应的API请求。
由于普通的js存在跨域问题,因此发送授权请求,必须在服务端运行。
而在kintone中,我们则可以使用kintone.proxy来代理发送授权请求,从而避免跨域问题。
那么,如果要改用安全性更高的kintone.plugin.app.proxy应该怎么写?
由于传递的参数类型是application/x-www-form-urlencoded,而且body的数据并不是Object,在调用kintone.plugin.app.proxy时会被 限制。
此时可以尝试将params放到url中,并将method改为GET来发送请求。
实测成功,然而并不采用,原因有二:
原因一:阿里云的官方并没有明确声明支持AssumeRole的Get请求,因此不推荐将其应用于实际的项目中。
原因二:kintone.plugin.app.proxy对比kintone.proxy的好处,仅仅是隐藏了accessKeyId。
而另一个关键参数accessKeySecret由于被指纹加密过,无法还原。
因此在安全性上,二者几无差别。
考虑到原因一的影响,在这种情况下还是推荐使用kintone.proxy。
最后
本文是在 使用“Tencent Cos for Kintone” 插件实现腾讯云+kintone文件管理 原有基础上的升级。既保证了安全性,又起到了扩容的效果。感兴趣的小伙伴都可以来试用一下!