Keep learning, keep living...

0%

OTPOne Time Password的缩写,是进一步加强身份认证的安全性的校验方法, 一般配合常规密码一起使用。OTP动态生成,只使用一次。手机验证码就是典型的OTP方式。用户请求一个短信验证码, 服务器随机生成一个验证码,然后临时存储起来,通过短信发送给用户。用户在系统上输入验证码发送回服务器,服务器根据临时存储的验证码进行校验。

生成OTP业内有许多标准算法,比如SHA-1。所有这些算法都包括两部分输入:

  • 种子值(seed):静态值,和帐号关联的密钥
  • 可变因子(moving factor):每次生成OTP都变化的部分
阅读全文 »

之前的文章<<使用eBPF和BCC调查创建文件的进程>>介绍了基于BCC来实现eBPF程序。BCC实现了对eBPF的封装,用户态部分提供Python API, 内核态部分使用的eBPF程序还是通过C语言来实现。运行时BCC会把eBPFC程序编译成字节码、加载到内核执行,最后再通过用户空间的前端程序获取执行状态。
可以在BCCBPF调用中,指定参数debug=4, 我们可以看到BCC的执行过程, 如:

1
2
3
4
5
6
7
from bcc import BPF
BPF(text="""
#include <linux/ptrace.h>
int kprobe__tty_write(struct pt_regs *ctx, struct file *file, const char __user *buf, size_t count) {
return 0;
}
""", debug=4)

eBPF编程的门槛还是比较高的,在当前还是快速发展的情况,API也还不稳定,对程序员的C语言、编译过程和内核等知识都有比较高的要求。BCC把这些都封装起来给用户提供了一个更为简单的使用框架。但本身也存在的一些问题,比如:

  • 每次执行时都需要重新编译
  • 执行程序的机器都需要安装内核头文件

eBPF: extended Berkeley Packet Filter是对BPF(现在称为cBPF: classic BPF)的扩展, 现在尽管还叫做BPF, 但应用场景已经远远超过了它的名称的范畴。它的应用范围的扩大主要得益于这几方面:

  • 内核中BPF字节码虚拟机扩展为一个通用的执行引擎
  • 执行可节码的安全校验
  • JIT支持,可以直接将字节码指令转成内核可执行的原生指令运行

这样在安全性、可编程性和性能方面的提升都使得eBPF在包过滤以外的其他领域获取巨大的应用空间。

阅读全文 »

在我们的一个CentOS 7.8系统上,/tmp目录下偶尔会有一些随机名称的文件产生。业务比较久远,已经不清楚这些文件的来源,需要确定是哪个进程在创建它们。

LinuxVFS: Virtual File System通用文件模型的方式在物理存储介质上的文件系统和用户接口之间建立一个虚拟文件系统的抽象层。其中最重要的两个结构是:

  • inode: 存放文件的一般信息,每个inode结构都有自己的编号,这个号码唯一标识了文件系统中的文件。
  • dentry: 存放文件名称信息,以及与对应文件进行链接的有关信息。

VFS创建文件的最主要的步骤是:

  1. 为要创建的文件创建一个dentry结构
  2. 为要创建的文件创建一个inode结构并分配inode编号
  3. inode编号和文件名映射关系保存在所分配的dentry结构中
  4. dentry写入到父目录的数据区
阅读全文 »

SOCKS是一个比较简单的通用代理协议,用于在客户端与服务器之间代理网络数据包。最新的版本是5, 所以一般叫做SOCKS5,协议规范是RFC1928。但SOCKS5并不兼容之前的SOCKS4SOCKS4ASOCKS5SOCKS4的基础上添加了UDP转发功能和校验机制。

SOCKS5的工作过程简单可以归纳为协商、请求、和转发三个阶段。以TCP代理场景来看, 一般流程是:

  1. 客户端建立TCP连接
  2. 客户端发送客户端侧支持的校验方法
  3. 服务端回应选择的校验方法
  4. 客户端和服务端之间按选择的校验方法完成校验
  5. 客户端发送所需的请求给服务端。SOCKS5支持不同的请求类型,包括CONNECT, BIND, UDPASSOCIATE等。
  6. 服务端接收到请求,从中解析出目的地址,建立到目的地址的连接。
  7. 发送成功信息给客户端。
  8. 客户端开始发送应用层信息。
  9. 服务端在客户端和目的地址之间转发应用层信息。

其中,2-4完成协商阶段,5-7完成请求阶段,之后进入转发阶段。

当前常用的校验方法是USERNAME/PASSWORD(RFC1929)和GSS-API(RFC1961),具体的校验过程是由不同校验方法来自定义的。

使用USERNAME/PASSWORD校验方法的TCP代理时序图如下:

阅读全文 »

我们的业务Docker镜像是在centos/systemd镜像基础上构建的,业务进程由systemd来启动。最近需要对业务逻辑进行改造,需要识别传入的环境变量。看上去是相当简单的改动,但在我们的进程中加入读取环境变量的逻辑却发现读取不到传入的变量内容,最终定位原因是在systemd的环境变量的处理。

阅读全文 »

在一台CentOS 7.0服务器(内核版本号:3.10.0-123.el7.x86_64)上安装我们的安全防护程序后,会出现curl访问网址超时5秒的情况。现象如下:

1
2
3
4
5
[root@localhost ~]# time curl -s www.baidu.com -o /dev/null

real 0m5.120s
user 0m0.002s
sys 0m0.009s

通过strace分析程序调用的过程:

1
strace -f -tt -o curl.strace curl -s www.baidu.com -o /dev/null

strace输出可以看到, 第一次curl调用sendmmsg同时发送了两个DNS数据包,分别是A记录和AAAA记录请求,但是只收到了A记录响应包:

然后等待5秒超时后,依次调用sendtorecvfrom串行处理两个DNS请求, 这次两个DNS响应全部收到后,继续向下执行:

而从抓包结果分析,tcpdump只能看到第一次同时发送的两个DNS请求中的A记录请求,AAAA记录请求数据包被内核协议栈丢弃了:

1
2
3
4
5
6
17:04:24.772049 IP 10.10.10.89.57416 > 114.114.114.114.53: 37081+ A? www.baidu.com. (31)
17:04:24.773693 IP 114.114.114.114.53 > 10.10.10.89.57416: 37081 3/0/0 CNAME www.a.shifen.com., A 180.101.49.12, A 180.101.49.11 (90)
17:04:29.776474 IP 10.10.10.89.57416 > 114.114.114.114.53: 37081+ A? www.baidu.com. (31)
17:04:29.778694 IP 114.114.114.114.53 > 10.10.10.89.57416: 37081 3/0/0 CNAME www.a.shifen.com., A 180.101.49.11, A 180.101.49.12 (90)
17:04:29.778925 IP 10.10.10.89.57416 > 114.114.114.114.53: 42471+ AAAA? www.baidu.com. (31)
17:04:29.780523 IP 114.114.114.114.53 > 10.10.10.89.57416: 42471 1/1/0 CNAME www.a.shifen.com. (115)
阅读全文 »

Kubernetes没有给本地环境(Bare-metal, On-Premise)提供负载均衡实现,LoadBalancer类型的服务主要在各大公有云厂商上能够得到原生支持。在本地环境创建LoadBalancer类型的服务后,服务的EXTERNAL-IP会一直处于<pending>状态。这是因为在本地环境没有相应的controller来处理这些LoadBalancer服务。比如:

1
2
3
4
[root@master1 vagrant]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.32.0.1 <none> 443/TCP 31d
whoami LoadBalancer 10.32.0.132 <pending> 80:31620/TCP 103s

之前的文章<<基于LVS DR模式的Kubernetes Service External-IP实现>>介绍了手动设置EXTERNAL-IP的方式实现外部负载均衡。本文通过在本地环境实现一个简单的controller来处理LoadBalancer类型服务自动实现负载均衡器。架构示意如图:

LoadBalancer是位于Kubernetes集群外的独立集群。可以通过ECMP将请求分散到不同的LoaderBalancer节点上,LoadBalancer再将请求分发到Kubernetesnode上。

阅读全文 »

之前的文章<<Kubernetes Service网络通信路径>>介绍了kubernetes的几种Service。如果要暴露服务给kubernetes集群外使用,可以选择NodePortLoadBalancer。但LoadBalancer现在主要在各大公有云厂商上能够原生支持。而使用NodePort暴露服务,将使用一个非常大的端口,无法使用原始的端口号来暴露服务,比如mysql3306端口。

Service官方文档中介绍了一种辅助方式, 叫External-IP, 可以在worker节点上会通过该IP来暴露服务,而且可以使用在任意类型的service上。集群外的用户就可以通过该IP来访问服务。但如果这个IP只存在于一个worker节点上,那么就不具备高可用的能力了,我们需要在多个worker节点上配置这个VIP:Virtual IP。我们可以使用LVS(也叫做IPVS)的DR(Director Routing)模式作为外部负载均衡器将流量分发到多个worker节点上,同时保持数据包的目的地址为该VIP

DR模式只会修改数据包的目的MAC地址为后端RealServerMAC地址,因而要求负载均衡器DirectorRealServer在同一个二层网络,而且响应包不会经过Director

阅读全文 »

之前的文章<<Kubernetes网络和CNI>><<Kubernetes flannel网络分析>>介绍了Kubernetes集群的pod网络的通信过程。

pod本质上非固定的,经常发生变化,而pod IPpod销毁和创建的时候会发生变更,因而不能直接对外提供服务。Kubernetes通过service资源来对外提供服务,serviceIP是固定的,它自动绑定一组pod并根据不同实现将流量转发到这些pod中, 并在流量转发的过程中实现负载均衡(load balance)。

Kubernetesnode节点上的主要组件有kube-proxykubelet, kubelet会调用相关的CNI实现完成POD网络的通信。而kube-proxy则负责上述的servicePOD之间的流量转发。实际上,在之前文章的实验环境里,即使把node节点上的kube-proxy组件都停止,也不会影响pod网络通信。

service本质就是将一组pod通过固定IP暴露给使用者,可以由ip:port:protocol来标识。

service主要以下几种类型:

  • ClusterIP: 用Kubernetes集群内部IP暴露服务,也就是说只有在Kubernetes集群内才可以访问这个service。这是默认的service类型。ClusterIP的范围是在kube-apiserver启动时通过-service-cluster-ip-range参数指定的。这些IP只能在kubernetes集群内进行访问。service的相关信息是在yaml文件中定义的,最终暴露的信息可表示为:

    1
    spec.clusterIp:spec.ports[*].port:spec.ports[*].protocol
  • NodePort: 在Kubernetes集群的所有node节点上使用相同的固定port来暴露服务。这种类型会自动创建ClusterIP类型的服务,NodePortservice会将流量转发到ClusterIP类型的服务。服务的使用者可以使用NodeIP:NodePort来访问该服务。这种类型服务暴露的信息可以表示为:

    1
    2
    <NodeIP>:spec.ports[*].nodePort:spec.ports[*].protocol
    spec.clusterIp:spec.ports[*].port:spec.ports[*].protocol
  • LoadBalancer: 是通过kubernetes集群外部设施所提供的IP来暴露服务。NodePortClusterIP类型的服务会被自动创建。不同的LoadBalancer负责实现外部IP:portNodePort服务的映射。这种类型暴露的信息可以表示为:

    1
    2
    3
    spec.loadBalancerIp:spec.ports[*].port:spec.ports[*].protocol
    <NodeIP>:spec.ports[*].nodePort:spec.ports[*].protocol
    spec.clusterIp:spec.ports[*].port:spec.ports[*].protocol
阅读全文 »

flannelcoreos开源的Kubernetes CNI实现。它使用etcd或者Kubernetes API存储整个集群的网络配置。每个kubernetes节点上运行flanneld组件,它从etcd或者Kubernetes API获取集群的网络地址空间,并在空间内获取一个subnet,该节点上的容器IP都从这个subnet中分配,从而保证不同节点上的IP不会冲突。flannel通过不同的backend来实现跨主机的容器网络通信,目前支持udp,vxlan,host-gw等一系列backend实现。本文介绍vxlan backend下的容器通信过程。

flannelv0.9.0版本上对vxlan的实现作了改动。源码中有一段非常详细的注释介绍了不同版本的设计与实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Some design notes and history:
// VXLAN encapsulates L2 packets (though flannel is L3 only so don't expect to be able to send L2 packets across hosts)
// The first versions of vxlan for flannel registered the flannel daemon as a handler for both "L2" and "L3" misses
// - When a container sends a packet to a new IP address on the flannel network (but on a different host) this generates
// an L2 miss (i.e. an ARP lookup)
// - The flannel daemon knows which flannel host the packet is destined for so it can supply the VTEP MAC to use.
// This is stored in the ARP table (with a timeout) to avoid constantly looking it up.
// - The packet can then be encapsulated but the host needs to know where to send it. This creates another callout from
// the kernal vxlan code to the flannel daemon to get the public IP that should be used for that VTEP (this gets called
// an L3 miss). The L2/L3 miss hooks are registered when the vxlan device is created. At the same time a device route
// is created to the whole flannel network so that non-local traffic is sent over the vxlan device.
//
// In this scheme the scaling of table entries (per host) is:
// - 1 route (for the configured network out the vxlan device)
// - One arp entry for each remote container that this host has recently contacted
// - One FDB entry for each remote host
//
// The second version of flannel vxlan removed the need for the L3MISS callout. When a new remote host is found (either
// during startup or when it's created), flannel simply adds the required entries so that no further lookup/callout is required.
//
//
// The latest version of the vxlan backend removes the need for the L2MISS too, which means that the flannel deamon is not
// listening for any netlink messages anymore. This improves reliability (no problems with timeouts if
// flannel crashes or restarts) and simplifies upgrades.
//
// How it works:
// Create the vxlan device but don't register for any L2MISS or L3MISS messages
// Then, as each remote host is discovered (either on startup or when they are added), do the following
// 1) create routing table entry for the remote subnet. It goes via the vxlan device but also specifies a next hop (of the remote flannel host).
// 2) Create a static ARP entry for the remote flannel host IP address (and the VTEP MAC)
// 3) Create an FDB entry with the VTEP MAC and the public IP of the remote flannel daemon.
//
// In this scheme the scaling of table entries is linear to the number of remote hosts - 1 route, 1 arp entry and 1 FDB entry per host
//
// In this newest scheme, there is also the option of skipping the use of vxlan for hosts that are on the same subnet,
// this is called "directRouting"

v0.9.0之前版本的实现主要依赖vxlan内核模块的L2MISSL3MISS消息机制。L2MISS是指vxlan设备在ARP表中找不到内层IP对应的MAC地址时会给用户态程序发送netlink消息。L3MISS是指vxlan设备在FDB表中找不到VXLAN协议内层MAC地址所属的VTEPIP地址时会给用户态程序发送netlink消息。之前的文章<<动态维护FDB表项实现VXLAN通信>>介绍过相关概念和操作。本文主要分析v0.9.0版本上的实现方式。

阅读全文 »