由于目前的情况*,*我们DeepSource的大部分人都在远程工作。这使得安全--更重要的是,方便--访问内部服务变得非常重要。我们曾经使用过OpenVPN,但它有很多不足之处。所以,请忍耐一下--让我们谈谈过去,因为只有这样,我们才能意识到我们真正走过了多远。
OpenVPN的设置
我们曾经在一个有公共IP的非集群VM机上运行OpenVPN。每次有人加入公司,都要手动配置一个新的客户证书,并通过密码管理器共享,然后将其导入OpenVPN连接应用程序。当他们离开时呢?我们只是希望我们能记得撤销该证书,当然了!"。
哦,但这还不是全部。为了实现仅限内部的服务,即只在VPN网络内解析的服务--我们在NGINX入口处将OpenVPN IP列入白名单:
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: XX.XX.XX.XX/32
通过VPN解决,但其他情况下是403。这是一个丑陋的黑客,它实际上是通过隐蔽性实现安全。OpenVPN也缺乏任何形式的易于配置的ACL或RBAC。总的来说,我们在这方面的安全态势并不是太好。
那么,我们需要什么呢?
VPN的目标
- 单点登录
- 使用集群DNS来解决内部名称--不再有入口白名单的黑客。
- 访问控制列表
- 对终端用户来说是无缝的
进入Tailscale
Tailscale是一个建立在WireGuard之上的网状VPN。我将跳过Tailscale如何工作的细节,因为Tailscale的优秀人员已经做了这些。提示:大量的NAT穿越巫术。但简而言之,它是JustWorks™ - 你猜怎么着,这正是我们需要的。

在Kubernetes中运行Tailscale
有两种方法可以做到这一点。
- 作为每个pod上的一个挎包,每个pod作为一个Tailscale客户端
- 作为一个子网路由器,将pod的CIDR公布给Tailnet
由于明显的原因,选项1是不可行的。在每个pod上附加一个sidecar容器,需要对我们所有的Helm图表进行大规模的重构,更不用说,我们必须分叉和维护所有的上游图表。所以,这是不可行的。
那我们就来谈谈方案二吧。
什么是子网路由器?它是你通常的Tailscale节点,即Tailnet上的一个客户端 - 除了它向网络上的所有其他节点公布一个指定的CIDR。有效地,该命令看起来像这样:
$ tailscale up --advertise-routes 10.136.0.0/20 # pod CIDR
但是,等等,如果任何人都可以公布路由,这不是一个潜在的安全风险吗?值得庆幸的是,每条被公告的路由都必须由Tailscale的网络管理员手动批准。
虽然我们写了自己的Helm图来运行Tailscale作为一个子网路由器,但公共的也是可以的:
tailscale-subnet-router-0 1/1 Running 0 3d14h
同样地,我们在生产集群上部署了另一个子网路由器。我可以听到你的惊恐的喘息声 - 你不要担心,我们会谈论为什么这不是一个问题,在一点点。
集群内的DNS
现在我们的集群网络是可访问的,不得不打入IP地址是不好玩的。幸运的是,Tailscale支持对自定义DNS名称服务器进行解析。然而,我们希望有两个DNS名称服务器来解决 - 一个在开发集群上,一个在prod上。幸运的是(X2!),Tailscale支持分割DNS。

这意味着任何*.deepsource.icu 域名将针对1.2.3.4(开发DNS)进行解析,而*.deepsource.xyz 将针对5.6.7.8(prod DNS)进行解析。互联网流量不受影响,因为你的本地DNS设置没有被触动。
现在,你可能想知道--这些名字是如何解析的?我们可以感谢CoreDNS的强大的rewrite 规则。在我们的案例中,它们看起来像这样:
.53 {
rewrite {
name regex (.*)\.(.*)\.deepsource\.icu {1}.{2}.svc.cluster.local answer auto
}
rewrite {
name regex (.*)\.deepsource\.icu {1}.default.svc.cluster.local answer auto
}
}
从本质上讲,我们试图将任何*.deepsource.icu 域解析为default 命名空间中的Kubernetes服务。所有其他命名空间都以service.namespace.deepsource.icu 的形式出现。
这里的一个问题是,不是所有的服务都听80端口,也不是所有的服务都有最漂亮的名字。一个快速的解决方法是用想要的名字和暴露的端口创建一个新的服务。
外部访问节点

碰巧的是,不是所有我们需要访问的机器都能安装Tailscale。一个很好的例子是谷歌Kubernetes引擎的控制平面端点。控制平面,在Kubernetes中,是一个管理组件,管理集群中的工作节点和吊舱。控制平面的访问可以限制在一个单一的原点IP,由于Tailscale是一个网状网络,没有单一的出口IP。
作为一个变通办法,我们把Tailscale安装在一个单独的计算虚拟机实例上*,*公布到这些端点的路由。这与我们在集群中公布吊舱CIDR的方式很相似。多么巧妙啊
ACL
最后,我们用Tailscale的ACL策略把一切联系起来。还记得我说过,把生产集群放在VPN网络上不一定是个坏主意吗?Tailscale的ACL使它成为可能。它们的好处是默认拒绝--所以除非你定义了一个"Action": "accept" 规则,否则资源是无法到达的。下面是我们的ACL策略的一个摘录:
...snip...
"ACLs": [
{
"Action": "accept",
"Users": ["group:infrastructure"],
"Ports": ["*:*"],
},
{
"Action": "accept",
"Users": ["group:language"],
"Ports": [
"somehost:420"
...
],
},
...snip...
为了简洁起见,我跳过了我们定义组和主机的部分。因此,group:infrastructure 组中的所有用户都可以访问所有的东西--所有的主机:端口;group:language 组中的用户只可以访问一些特定的主机:端口。
定义ACL可能是最烦人的部分--而且没有什么简单的方法可以做到这一点。冷处理,取消所有的访问权限,授予最低限度的访问权限,等待大量的直接信息,说明他们不能到达哪个主机,然后授予这些访问权限。它最终会放慢速度,这意味着每个人都可以接触到他们通常接触到的东西。
结束语
切换到Tailscale,大大改善了我们的安全态势,同时提高了用户/开发人员的便利性。随着Tailscale在Kubernetes中运行,我们可以即时访问所有集群服务。本地开发从未如此简单。想把你的应用程序指向Redis或RabbitMQ?只需使用{redis,rabbitmq}.deepsource.icu 。
但仍有改进的余地:在未来,我们希望建立一个小型服务,以快速跳转到内部URL(想想看:GoLinks)。还有很多工作要做,以简化我们的本地开发环境,而Tailscale在这方面可以很方便。
综上所述,Tailscale为安全软件的设计树立了一个良好的榜样,我们非常感谢它。