freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

vArmor安全 | 代码学习上
2024-12-09 16:55:48
所属地 浙江省

vArmor

vArmor是字节跳动推出的一款容器安全解决方案,旨在通过强制访问控制来保护容器的安全。vArmor的实现依赖于Linux的eBPF和LSM(Linux Security Module)技术。其核心目标是为容器提供一种高效的安全模型,允许用户通过配置规则来控制容器的行为。它可以用于增强容器隔离性、减少内核攻击面、增加容器逃逸或横行移动攻击的难度与成本。

vArmor 的特色

  • Cloud-Native. vArmor 遵循 Kubernetes Operator 设计模式,用户可通过操作 CRD API对特定的 Workloads 进行加固。从而以更贴近业务的视角,实现对容器化微服务的沙箱加固。
  • Multiple Enforcers. vArmor 将 AppArmor、BPF、Seccomp 抽象为 Enforcer,并支持单独或组合使用,从而对容器的文件访问、进程执行、网络外联、系统调用等进行访问控制。
  • Allow-by-Default. vArmor 当前重点支持此安全模型。即只有显式声明的行为会被阻断,从而减少性能损失和增加易用性。
  • Built-in Rules. vArmor 提供了一系列开箱即用的内置规则。这些规则为 Allow-by-Default 安全模型设计,从而极大降低对用户专业知识的要求。
  • Behavior Modeling. vArmor 支持对工作负载进行行为建模。这可用于开发白名单安全策略、分析哪些内置规则可用于加固应用、指导工作负载的配置遵循权限最小化原则。
  • Deny-by-Default. vArmor 可以基于行为模型创建白名单安全策略,从而确保仅显式声明的行为被允许。

技术实现

vArmor通过以下技术实现云原生容器沙箱

1. 强制访问控制

借助 Linux 的 AppArmor或 BPF LSM(Linux Security Module),vArmor 在内核中对容器进程实施强制访问控制。这包括对文件、程序、网络外联等的管理。通过这种方式,vArmor 能够有效地防止未授权访问,确保容器的安全性。

2. 安全模型

为了减少性能损失并提高易用性,vArmor 采用 Allow by Default的安全模型。这意味着只有显式声明的行为会被阻断,而其他所有行为默认被允许。这种设计降低了误报率,使用户体验更加流畅。

3. 用户操作与沙箱策略

用户可以通过操作 CRD(Custom Resource Definitions)实现对指定工作负载中容器的沙箱加固。用户能够选择和配置沙箱策略,包括预置策略和自定义策略。预置策略包含一些常见的提权阻断和渗透入侵防御策略,使用户能够快速部署有效的安全措施。

4. vArmor 的实现

本部分主要关注 vArmor 如何利用 eBPF 中的 LSM 技术实现对容器的加固。vArmor 的内核代码存放在一个单独的仓库 vArmor-ebpf中。

在 vArmor-ebpf中,存在两个主要目录:behavior和 bpfenforcer

behavior

behavior就是观察模式,主要用于观察和分析容器的行为,而不对其进行任何阻断。其设计目标是收集运行时信息,以便进行审计和安全监控。

behavior模块的核心功能包括:

  • 事件捕获:通过raw tracepoints捕获内核中的特定事件,例如进程创建和执行。
  • 数据记录:将捕获到的事件数据记录下来,以便进行后续分析和审计。
  • 实时监控:支持实时监控容器的行为,方便运维人员及时发现异常。

behavior模块的核心入口文件是tracer.c,在此文件中定义了多个raw tracepoint事件。以下是对tracer.csched_process_exec事件的详细分析。

SEC("raw_tracepoint/sched_process_exec")
int tracepoint__sched__sched_process_exec(struct bpf_raw_tracepoint_args *ctx) {
    struct task_struct *current = (struct task_struct *)ctx->args[0];
    struct linux_binprm *bprm = (struct linux_binprm *)ctx->args[2];
    
    struct task_struct *parent = BPF_CORE_READ(current, parent);
    struct event event = {};

    // 填充event结构体
    event.type = 2;
    BPF_CORE_READ_INTO(&event.parent_pid, parent, pid);
    BPF_CORE_READ_INTO(&event.parent_tgid, parent, tgid);
    BPF_CORE_READ_STR_INTO(&event.parent_task, parent, comm);
    BPF_CORE_READ_INTO(&event.child_pid, current, pid);
    BPF_CORE_READ_INTO(&event.child_tgid, current, tgid);
    BPF_CORE_READ_STR_INTO(&event.child_task, current, comm);
    bpf_probe_read_kernel_str(&event.filename, sizeof(event.filename), BPF_CORE_READ(bprm, filename));

    // 处理环境变量
    u64 env_start = 0;
    u64 env_end = 0;
    int i = 0;
    int len = 0;

    BPF_CORE_READ_INTO(&env_start, current, mm, env_start);
    BPF_CORE_READ_INTO(&env_end, current, mm, env_end);
    
    while(i < MAX_ENV_EXTRACT_LOOP_COUNT && env_start < env_end ) {
        len = bpf_probe_read_user_str(&event.env, sizeof(event.env), (void *)env_start);
        if (len <= 0) {
            break;
        }
        env_start += len;
        event.env[0] = 0; // 清空env数组
        i++;
    }

    event.num = i;        
    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
    return 0;
}

在这个函数中,sched_process_exec事件会在进程执行新的可执行文件时触发。其主要逻辑包括:

  • 获取当前进程和父进程信息:利用ctx->args获取当前执行的进程及其父进程的信息。
  • 填充事件结构体:将相关的进程信息(如PID、TGID、进程名称)以及执行的文件名填充到自定义的event结构体中。
  • 环境变量处理:读取当前进程的环境变量,并将其存储在事件结构体中。
  • 将事件输出到用户空间:通过bpf_perf_event_output将事件信息传递到用户空间的事件处理程序。

在用户空间,behavior模块会创建一个事件读取器,用于读取从内核空间传递来的事件数据。以下是事件处理的示例代码:

func (tracer *Tracer) createBpfEventsReader() error {
    reader, err := perf.NewReader(tracer.objs.Events, 8192*128)
    if err != nil {
        return err
    }
    tracer.reader = reader
    return nil
}

func (tracer *Tracer) handleTraceEvents() {
    var event bpfEvent
    for {
        record, err := tracer.reader.Read()
        if err != nil {
            continue
        }
        if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
            continue
        }

        // 将解析后的事件发送到其他goroutine进行处理
        for _, eventCh := range tracer.bpfEventChs {
            eventCh <- event
        }
    }
}

在这个代码示例中,createBpfEventsReader函数创建了一个事件读取器,而handleTraceEvents则是一个循环,持续读取事件,解析后将其传递给其他处理逻辑。

bpfenforcer

bpfenforcer负责将eBPF程序加载到内核中并执行安全策略。以下是该模块的详细分析。

在bpfenforcer中,eBPF程序的加载主要通过initBPF()函数实现。该函数会调用loadBpfObjects()来加载预编译的eBPF程序和映射。

func (enforcer *Enforcer) initBPF() error {
    tracer.log.Info("load bpf program and maps into the kernel")
    if err := loadBpfObjects(&enforcer.objs, nil); err != nil {
        return fmt.Errorf("loadBpfObjects() failed: %v", err)
    }
    return nil
}

AttachLSM()函数用于将eBPF程序附加到LSM钩子点。以VarmorSocketConnect为例,代码如下:



sockConnLink, err := link.AttachLSM(link.LSMOptions{
    Program: enforcer.objs.VarmorSocketConnect,
})
if err != nil {
    return err
}
enforcer.sockConnLink = sockConnLink

此代码段将VarmorSocketConnect程序与内核的socket连接事件关联,使得该程序能够在相应的事件发生时被调用。

netInnerMap是用于保存网络规则的内存结构,其定义如下:



netInnerMap := ebpf.MapSpec{
    Name:       "v_net_inner_",
    Type:       ebpf.Hash,
    KeySize:    4,
    ValueSize:  4*2 + 16*2,
    MaxEntries: uint32(varmortypes.MaxBpfNetworkRuleCount),
}
collectionSpec.Maps["v_net_outer"].InnerMap = &netInnerMap

在内核中,规则通过v_net_outerv_net_inner两个BPF映射进行管理。v_net_outer是一个哈希表,存储了与namespace相关的规则集合。

struct {
    __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
    __uint(max_entries, OUTER_MAP_ENTRIES_MAX);
    __type(key, u32);
    __type(value, u32);
} v_net_outer SEC(".maps");





在用户态,通过enforcer模块设置规则,并将其放入v_net_inner中。



func (tracer *Tracer) createBpfEventsReader() error {
reader, err := perf.NewReader(tracer.objs.Events, 8192*128)
if err != nil {
return err
}
tracer.reader = reader
return nil
}

func (tracer *Tracer) handleTraceEvents() {
var event bpfEvent
for {
record, err := tracer.reader.Read()
if err != nil {
continue
}
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
continue
}

// 将解析后的事件发送到其他goroutine进行处理
for _, eventCh := range tracer.bpfEventChs {
eventCh <- event
}
}
}


behavior与bpfenforcer的关系

  • 观察与控制behavior模块主要用于观察和记录容器的行为,而bpfenforcer模块则负责实施安全策略,对特定行为进行阻断。两者结合实现了全面的安全监控和控制。
  • 数据反馈behavior模块所收集的事件数据可以用来分析和优化bpfenforcer模块的规则设置,提高安全性和性能。

总结

vArmor通过eBPFLSM机制,实现了对容器的加固。通过behaviorbpfenforcer两种模式,可以实现观察模式和阻断模式。bpfenforcer模块负责加载eBPF程序并实施强制访问控制,而behavior模块则用于监控和记录事件。这种架构使得vArmor能够高效地对容器行为进行管理和控制。

参考

# 云原生安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录