专栏首页网关【云+社区年度征文】腾讯防疫健康码-远程协作环境优化
原创

【云+社区年度征文】腾讯防疫健康码-远程协作环境优化

背景

由于年初新冠疫情爆发,我参与了腾讯防疫健康码的项目研发工作中。疫情健康码项目无疑是非常成功的,它覆盖9亿+人口和300+市县。但是项目的研发过程确实非常艰辛,该项目团队成员是在疫情期间临时组建起来的。疫情健康码项目研发团队由腾讯云同学主导+腾讯志愿者协助+合作伙伴公司的同学组成。大家都是远程在家办公,因此工作中也遇到了一系列的问题。还好有腾讯众多产品的保驾护航,才让项目能够高效成功落地,下面我从个人的研发视角剖析一下远程办公项的痛点,以及我们是怎么解决问题的。

跨公司远程研发团队的痛点

  1. 沟通低效,远程沟通以IM聊天工具为主,例如:微信;
  2. 项目协作困难,一般工作内部团队有内部的项目管理工作,但跨公司协时却不能用谁家内部的系统;
  3. 版本控制工具,代码版本控制工具不好选,要既可以远程团队共享权限,也可以保证代码安全;
  4. 开发调试环境,要解决远程开发调试代码、查询数据库、日志等问题。

上述的几个问题大部分通过腾讯的Saas产品很好的解决了。例如:

  • 企业微信,打通个人微信和企业微信,我们和外部合作时,不需要添加个人微信,使用企业微信即可沟通、拉群等;
  • TAPD,一站式敏捷研发协作云平台, 免费高效、很好的解决了敏捷开发的需求管理、任务管理、状态流转、项目文档共享沉淀等需求,支持企业微信登录;
  • 腾讯文档,腾讯文档支持在线多人同时编辑协作和多种文档格式,且可以设置安全权限,很好的项目资料共享和项目灵魂管理的问题,支持微信扫描等登录;
  • 腾讯工蜂,代码版本控制工具,完全兼容github,免费高效,权限可以自定义,支持微信扫描等登录。
  • 腾讯会议,腾讯会议支持远程多人语音、视频、共享桌面等,且免费高效、稳定,很好的解决了我们的远程会议需求。

上述的几个产品在我们项目中频繁使用,对我们的项目研发管理协作起到了非常积极的促进作用。

但是远程办公对开发同学还是不友好的,我们使用腾讯的云产品作为项目的开发环境,例如:mysql、redis、es等存储服务。很多开发同学习惯了本地调试代码,即本地起应用连腾讯云的存储服务,使用腾讯云产品作为开发环境时,需要解决公网用户连接腾讯云网络连通权限。例如用户开发的应用A,需要连接mysql 和 es 存储服务来开发调试,那么需要给用户开通他个人出口IP到mysql 和 es 的白名单和3306端口以及9200端口权限。那么可能存在这么几个问题:

  1. 个人家庭的网络出口IP大部分是动态分配的,每天都会变更,导致IP白名单每天要手动更新;
  2. 部分腾讯云产品只能支持开通有限数量的白名单IP,例如es最多只能同时配置10个IP地址,导致操作10人的团队不能同时使用ES,且经常要动态调整;
  3. 共享出口IP的网络会增加网络安全风险。

怎么解决开发的网络问题

问题核心在于远程办公大家都在公网环境,腾讯云服务不能对公网完全开通安全策略,这和裸奔没啥区别。怎么解决呢?因为我之前在研发网关产品,所有我首先想到的就是准入网关的方式来解决,也可以理解成安全网络代理,如下图:

image.png
  1. 找一台有固定出口IP的云服务器作为准入网关;
  2. 腾讯云服务对网关开放IP和指定端口权限;
  3. 客户端请求先到网关进行安全验证;
  4. 验证后转发到指定权限的IP和端口。

网关的转发安全策略可以用如下伪代码表示:

let urls = {
"msyql_host:3306": 1,
"es_host:9200": 1
}
let user = {
        "userA":"tokenA",
         "userB":"tokenB",
}
let ip = {
        "ipA": 1,
        "ipB":1
}
if (urls[req.url] && (authUser(req) || ip[req.remoteAddress])) {
        socket.connect(req.url); // 连接目标服务
        socket.pipe(req.socket); // 管道转发
}

function authUser(req) {
        // 可根据客户选择的算法支持,basic auth 或其他自定义算法判断请求是否合法,return 1 or 0
}

可以直接去找一些开源代理来实现网关功能。

上面网关完成部署之后,我们的代码怎么使用呢?

假设网关支持基本认证(Basic access authentication)和自定义ip白名单的方式(自定义白名单至少解决ip数量的限制), 由于参与的防疫小程序项目使用的是Java作为研发语言,我首先想到的是配置,配置jdk参数方式让应用程序请求远程网络时使用代理。

配置jvm启动时参数

指定使用代理通信: -DsocksProxyHost=xxx.xxx.xxx.xxx -DsocksProxyPort=1080 -Djava.net.socks.username=xxx -Djava.net.socks.password=xxx 详细可以参考:

jdk8: https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html

jdk11: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/doc-files/net-properties.html#Proxies

找到Jdk源码配置参数使用的类是:DefaultProxySelector.java,由于我们用的是jdk8,因此不支持配置代理基本认证即配置:用户名和密码设置无效,也可以自定义实现Authenticator类,但这种方式会侵代码,下面是JDK11中DefaultProxySelector.java 设置用户名和密码的代码片段。

Authenticator.setDefault (new Authenticator() {

   protected PasswordAuthentication getPasswordAuthentication() {

   return new PasswordAuthentication ("username", "password".toCharArray());

   }

});

由于上述原因就放弃基本认证方式,而是到网关验证客户端ip。

代理之后es 客户端还是有问题,我这边用的ES客户端版本如下:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-high-level-client</artifactId>
	<version>6.4.3</version>
</dependency>

这个版本的client请求es没有走代理,初次发现问题是es实例化http客户端时,没有用到系统属性,需要显示调用,代码片段如下:

builder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
    @Override
    public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
        httpClientBuilder.useSystemProperties(); //显示调用使用系统属性。
        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    }
});

显示调用之后,由于es客户端是http请求,还需添加jvm情动参数 -Dhttp.ProxyHost=xxx.xxx.xxx.x xx-Dhttp.ProxyPort=8080 , es客户端才能正常使用代理。

使用第三方软件客户端代理

例如:proxifier代理软件,该方式最方便,直接使用网关作为http代理,配置客户端将需要连的中间件都走到代理,工程代码不需要任何配置和代理的改动。但是有两个问题:

  1. 软件可能收费软件,需要自己想办法激活;
  2. 只支持使用代理的basic auth方式验证,安全性一般。

自己开发客户端代理映射

该方式自己开发网关客户端,监控本地指定端口,转发到http代理上,这种方式需要将工程里的中间件配置修改指向本地127.0.0.1和映射的端口,如果是https的中间还需要配置hosts,但该方式可以自定义安全策略,灵活自己设计签名算法,较为安全。客户端实现代码片段如下:

const map = {
        {
  "3306": {
    "proxy": "gateway.com:443",
    "auth": "", //认证签名串
    "target": "mysql_host:3306"
  },
  "9200": {
    "proxy": "gateway.com:443",
    "auth": "", //认证签名串
    "target": "es_host:9200"
  }
}
for (const port in map) {
  const cfg = map[port];
  const pxyopt = (i => ({ host: i[0], port: i[1]}))(cfg.proxy.split(':'));
  const server = net.createServer(socket => {
    if (pxyopt['port'] !== '443') {
      const proxy = net.connect(pxyopt, () => {
      var timestamp = (Date.now() / 1000).toFixed();
      var random = Math.floor(Math.random() * 10000);
      var result = signature(cfg.auth, random, timestamp);
      var pxyauth = result ? `Proxy-Authorization: Basic ${result}\r\n` : '';
      proxy.write(`CONNECT ${cfg.target} HTTP/1.1\r\nHost: ${cfg.target}\r\n${pxyauth}\r\n`);
      proxy.once('data', d => {
        let s = d.toString();
        if (s.startsWith('HTTP/1.1 200 ')) {
          setImmediate(() => socket.pipe(proxy).pipe(socket));
        } else {
          proxy.destroy(new Error(s));
        }
      });
      });
      var onerr = err => {
        socket.destroy();
        proxy.destroy();
        log(err.message);
      };
      socket.on('error', onerr)
      socket.setTimeout(TIMEOUT, () => proxy.destroy(new Error('timeout')));
      proxy.on('error', onerr);
      proxy.setTimeout(TIMEOUT, () => proxy.destroy(new Error('timeout')))
    } else {
      var timestamp = (Date.now() / 1000).toFixed();
      var random = Math.floor(Math.random() * 10000);
      var result = signature(cfg.auth, random, timestamp);
      var options = {
        hostname : pxyopt['host'],
        port     : pxyopt['port'],
        path     : cfg.target,
        method     : 'CONNECT',
        headers: {
          'Proxy-Authorization': `Basic ${result}`
        }
      };
      var req = https.request(options);
      req.on('connect', function(res, skt) {
        setImmediate(() => socket.pipe(skt).pipe(socket));
        socket.on('end', function() {
          console.log('socket end.');
        });
        var onerr = err => {
          socket.destroy();
          skt.destroy();
          log(err.message);
        };
        socket.on('error', onerr)
        socket.setTimeout(TIMEOUT, () => skt.destroy(new Error('timeout')));
        skt.on('error', onerr);
        skt.setTimeout(TIMEOUT, () => skt.destroy(new Error('timeout')))
      });
      req.end();
    }
   
    
  }).listen(port, '127.0.0.1', () => console.log(`127.0.0.1:${port} => ${cfg.proxy} => ${cfg.target}`));
  server.timeout = TIMEOUT;
}

function signature(appkey, random, timestamp) {
// 自定义签名算法
}

最后我们研发团队使用了自研客户端的方式,nodejs实现,支持可以打包成mac、linux、windows 等多平台运行。使用起来也方便。

1、我们先给每个项目研发成员分配个人的auth签名;

2、将每个人的auth签名配置到网关上;

3、网关认证用户来源是否合法。

上述的实现只是简单的认证了签名的方式,我们还可以拓展自定义更多灵活的安全策略。到此开发环境可以比较方便且安全的连上腾讯云服务了。

总结

  1. 我的本意是希望http(s), socket 通信都通过我搭建的HTTP代理路由, 所以设置了这三种通信的代理都指向一个host&port, 虽然说其他socket通信业可以通过http隧道转发,但是一些客户端(mysql、redis)不会应用到system.setPropety, 导致设置的代理无效;
  2. es client的实现也是有些问题,跟进去看请求开始是选到了http做代理,可对代理创建socket的时候还会选一次代理因为又配置了socks proxy,所有代理又使用了代理来进行转发,而我搭建的代理是http的,所有最后看上去都用了socks proxy,都会失败;
  3. 我这边使用httpclient4 配置sockets 就直接生效了使用es client 不知道为何一只不生效,必须在配置一次http代理;
  4. 还有没有解决的问题就是jdk启动参数使用代理的用户名和密码一直设置无效,必须自定义入侵项目的方式实现;
  5. 使用自定义的方式监听本地端口的方式来实现最好用,不仅仅是程序可以使用网关,我们使用的任何msyql客户端、浏览器等都可以支持,用户无感知且安全。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 全链条精准可溯,腾讯冷链追溯平台落地多省市,还上央视了!

    针对最近大家格外关注的冷链食品疫情防控,昨天的央视新闻带来了一则振奋人心的消息——一些企业近期使用冷链食品溯源码,实现全环节精准管理。与普通的溯源码相比,冷链食...

    腾讯安全
  • 一不小心就日均通话超30亿分钟了...

    PC客户端、手机App端、小程序、web端、电话……腾讯会议有多种接入方式,这时腾讯在音视频领域长期积累的云流控引擎技术派上了用场,通过融合经典信号处理、心理听...

    shixin
  • 高校复课如何科学应对?腾讯教育给出智慧化防疫解决方案

    ? 2020年4月10日,由腾讯智慧高校、企业微信和腾讯智慧教育发展研究中心联合主办的“后疫情时期高校复学防控指南解读”主题沙龙云端开讲。 ? 本次沙龙,围绕...

    腾讯智慧教育
  • 48亿+,健康守护者的故事

    一年前,中国还笼罩在疫情的阴影之下。 全国各地相继沦陷,感染人数与日俱增。面对未知的病毒,骤然间,举国禁足闭户,生产暂时停摆。 北京,中国的首都。 由于是政...

    腾讯云数据库 TencentDB
  • 腾讯刘胜义在联合国:新基建将构建更能抵御风险的弹性社会

    ? 7月1日,由联合国经济和社会事务部 (UN DESA)主办的“如何利用信息和通信技术及数字政府创新性地抗击新冠肺炎——非洲的应对方案”会议在线上举办。联...

    鹅老师
  • 道一云“全员报平安”入选广州市2020年度创新产品目录第一批(疫情防控应急类)|腾讯SaaS加速器·学员动态

    ? 来源 |  腾讯SaaS加速器一期项目-道一云 ---- 近日,广州市科学技术局公布了《广州市2020年度创新产品目录第一批(疫情防控应急类)入选产品名单...

    腾讯SaaS加速器
  • 产业互联网:中国新冠疫情狙击战的中坚力量

    ? ? 全球抗疫形势依然严峻,今日,WEF(世界经济论坛)官方刊发腾讯集团高级执行副总裁兼集团市场与全球品牌主席刘胜义署名文章,介绍产业互联网科技抗疫经验。 ...

    鹅老师
  • 大会活动 | AIIA2020 腾讯云王磊:连接产业和开发者,构建更完整的AI

    9月28日,在2020 AIIA人工智能开发者大会上,腾讯云AI计算机视觉产品中心总经理王磊表示:“在AI已经进入产业和生态角逐的当下,腾讯云希望连接产业和开发...

    优图实验室
  • 腾讯云智服确保战“疫”期间服务不间断

    ? 导语: “腾讯云智服”是开放腾讯内部十多年产品积淀的专业客服平台,战“疫”期间,正是这款利器,支持腾讯客服部几千名客服实现了远程服务,并在请求量暴涨45%...

    腾讯技术工程官方号
  • 腾讯AI医疗窘境

    AI被认为是新时代解放劳力型工作,提高工作效率、服务质量的最佳手段。所以,当医疗和AI结合,国内外医疗资源紧张的“毛病”有了治愈的曙光,只是医疗系统自上而下的A...

    刘旷
  • 远程办公经验为0,如何将日常工作平滑过度到线上?

    我是一名创业者,我的公司(深圳市友浩达科技有限公司)在2018年8月8日开始运营,现在还属于微型公司。这个春节假期,我一直十分关注疫情动向,也非常关心其对公司带...

    TVP官方团队
  • 智能战疫昨日精选:美国科研团队首次绘制出新冠病毒关键蛋白分子3D结构;中小企业三项社保缴费部分免征

    机器之心最新推出「智能战疫日报」,围绕「人工智能直接应用于抗击疫情」和「人工智能助力产业应对非常时期各项困难」两大主题,提供相关政策、人工智能应用和解决方案、行...

    机器之心
  • 理才网陈谏:疫情过后 “to B”产业互联网必将点燃|腾讯SaaS加速器·CEO说

    ? 来源:凤凰网   ---- 2020年伊始,全国人民众志成城、共克时艰,打响了一场没有硝烟的新型冠状病毒防疫战。在积极做好疫情防控工作的前提下,广东省及广...

    腾讯SaaS加速器
  • 腾讯健康码 16 亿亮码背后的 Elasticsearch 系统调优实践

    ? Elasticsearch(以下简称 ES)是近年来炙手可热的开源分布式搜索分析引擎,通过简单部署,就可以轻松实现日志实时分析、全文检索、结构化数据分析等...

    腾讯技术工程官方号
  • 远程办公经验为0,如何将日常工作平滑过度到线上?

    导语 | 受到疫情影响,很多企业开始考虑远程办公。近日,TVP群里的各位老师们对此话题展开了热烈讨论。TVP张善友老师作为一名创业者,也决定开启远程办公。本文是...

    尾尾
  • 腾讯健康码16亿亮码背后的Elasticsearch系统调优实践

    Elasticsearch(以下简称ES)是近年来炙手可热的开源分布式搜索分析引擎,通过简单部署,就可以轻松实现日志实时分析、全文检索、结构化数据分析等多重诉求...

    腾讯云大数据
  • 技术战“疫”:人工智能、5G、实时音视频、大数据的技术阅兵式

    2003年面对“SARS(非典)”时,移动通信还处于2G时代、网上冲浪还在用ADSL拨号,短信、电话是SARS期间人们沟通以及信息传达的最重要的工具,人们被迫由...

    shixin
  • 腾讯安全多项产品获全球著名机构推荐,全力护航产业安全抗“疫”

    2020年开年,我们共同经历了疫情来袭、假期延长、出行受限到社会秩序逐步恢复、全民联防联控抗击疫情、各行各业复工复产。面对突如其来的疫情和旷日持久的“战疫”过程...

    腾讯安全
  • 腾讯安全多项产品获全球著名机构推荐,全力护航产业安全抗“疫”

    2020年开年,我们共同经历了疫情来袭、假期延长、出行受限到社会秩序逐步恢复、全民联防联控抗击疫情、各行各业复工复产。面对突如其来的疫情和旷日持久的“战疫”过程...

    腾讯安全

扫码关注云+社区

领取腾讯云代金券