Skip to content

第一百六十六章:AppArmor

渡劫期

涉及内核源码:

LSM 框架的大道之上,林小源还未走远,便听到身后传来一个轻快的声音。

"等等!你还没认识我呢。"

林小源回头,看到一个穿着素白长袍的年轻修士,面容清秀,腰间挂着一卷竹简。与 SELinux 那位浑身刻满标签的铁面判官不同,这人看起来随和得多。

"你是?"

"AppArmor,"那人拱手一笑,"路径型安全模块。你别看我简单,在 Ubuntu 那边,我才是默认的守护者。"

林小源好奇地打量他。AppArmor 的竹简上没有标签,只写着一行行清晰的路径——/var/www/html/** r,/var/log/httpd/** w,

"你的规则……直接写路径?"

"对,"AppArmor 展开竹简,"不像 SELinux 那样要给每个文件贴标签。我直接告诉进程:你能访问哪些路径,不能访问哪些。简单明了。"

他指着竹简上的条目:"你看,这个 httpd 的 profile——/var/www/html/** r 只读网页,/var/log/httpd/** wa 可以写日志和追加,/etc/httpd/conf/** r 只读配置。一目了然。"

林小源点点头,但随即皱眉:"可是……如果文件被移动了呢?比如有人把网页从 /var/www/html/ 移到了 /srv/www/?"

AppArmor 的笑容微微一僵。

"这……确实是个问题。我认的是路径,不是文件本身。路径一变,规则就失效了。"

"所以 SELinux 的标签方式更可靠?"

AppArmor 沉默片刻,然后坦然道:"在精确性上,是的。但简单也有简单的好处——上手快,维护容易。在容器和桌面场景下,我的路径规则够用了。你不需要为每个文件打标签,一条通配符就能覆盖整个目录。"

c
/*
 * AppArmor vs SELinux:
 *
 * SELinux:
 *   基于标签 (type)
 *   需要给每个文件设置标签
 *   复杂但精确
 *
 * AppArmor:
 *   基于路径
 *   profile 定义可访问路径
 *   简单但路径变化可能失效
 *
 * AppArmor profile 示例:
 *   /usr/sbin/httpd {
 *     /var/www/html/** r,
 *     /var/log/httpd/** w,
 *     /etc/httpd/conf/** r,
 *     network inet tcp,
 *     capability net_bind_service,
 *   }
 *
 * profile 模式:
 *   enforce — 强制执行
 *   complain — 只记录
 *
 * 工具:
 *   aa-enforce — 设置 enforce 模式
 *   aa-complain — 设置 complain 模式
 *   aa-genprof — 生成 profile
 *   aa-logprof — 从日志生成规则
 */

/* 模拟 AppArmor profile */
struct profile_rule {
    char path[128];
    char perms[32]; /* r, w, x, a (append) */
};

struct aa_profile {
    char name[64];
    char mode[16]; /* enforce, complain */
    struct profile_rule rules[10];
    int rule_count;
};

void add_rule(struct aa_profile *p, const char *path, const char *perms) {
    if (p->rule_count >= 10) return;
    strncpy(p->rules[p->rule_count].path, path, 127);
    strncpy(p->rules[p->rule_count].perms, perms, 31);
    p->rule_count++;
}

int check_access(struct aa_profile *p, const char *path, char perm) {
    for (int i = 0; i < p->rule_count; i++) {
        /* 简化的路径匹配 */
        if (strstr(path, p->rules[i].path + 1) != NULL ||
            strstr(p->rules[i].path, path) != NULL) {
            if (strchr(p->rules[i].perms, perm))
                return 1; /* 允许 */
        }
    }
    return 0; /* 拒绝 */
}

void print_profile(struct aa_profile *p) {
    printf("Profile: %s (%s)\n", p->name, p->mode);
    for (int i = 0; i < p->rule_count; i++) {
        printf("  %s %s\n", p->rules[i].path, p->rules[i].perms);
    }
}

printf("=== AppArmor — 路径型安全 ===\n\n");

/* 创建 httpd profile */
struct aa_profile httpd = {
    .name = "/usr/sbin/httpd",
    .mode = "enforce",
    .rule_count = 0
};

add_rule(&httpd, "/var/www/html/", "r");
add_rule(&httpd, "/var/log/httpd/", "wa");
add_rule(&httpd, "/etc/httpd/conf/", "r");
add_rule(&httpd, "/tmp/", "rw");

printf("--- AppArmor Profile ---\n");
print_profile(&httpd);

printf("\n--- 访问检查 ---\n");
/* 测试不同的访问 */
struct {
    const char *path;
    char perm;
    const char *desc;
} tests[] = {
    {"/var/www/html/index.html", 'r', "读取网页"},
    {"/var/www/html/index.html", 'w', "写入网页"},
    {"/var/log/httpd/access.log", 'w', "写入日志"},
    {"/etc/passwd", 'r', "读取密码文件"},
    {"/tmp/sess_abc123", 'r', "读取 session"},
};

int n = sizeof(tests) / sizeof(tests[0]);
for (int i = 0; i < n; i++) {
    int allowed = check_access(&httpd, tests[i].path, tests[i].perm);
    printf("%s: %s\n", tests[i].desc, allowed ? "允许" : "拒绝");
}

printf("\n--- AppArmor vs SELinux ---\n");
printf("AppArmor:\n");
printf("  基于路径\n");
printf("  简单易用\n");
printf("  路径变化可能失效\n\n");
printf("SELinux:\n");
printf("  基于标签\n");
printf("  复杂精确\n");
printf("  不受路径变化影响\n\n");

printf("--- 使用场景 ---\n");
printf("AppArmor:\n");
printf("  Ubuntu 默认\n");
printf("  容器安全\n");
printf("  桌面应用\n\n");
printf("SELinux:\n");
printf("  RHEL/CentOS 默认\n");
printf("  服务器\n");
printf("  高安全需求\n");

AppArmor 带着林小源来到一处容器营地前。营地四周立着半透明的光幕,上面浮动着路径规则。

"你看,"AppArmor 指向营地内一个正在运行的 httpd 进程,"我给它加载了 enforce 模式的 profile。它只能读网页目录、写日志目录、读配置目录。别的路径,一概碰不了。"

林小源注意到营地角落里有一个测试进程试图访问 /etc/passwd,光幕立刻闪烁红光,将其挡了回来。

"但如果有人在 complain 模式下呢?"

AppArmor 摸了摸下巴:"complain 模式只记录不阻止。开发的时候用它,看看进程需要哪些权限,再据此写 profile。写好了,切成 enforce。"

"像是一种……试探?"

"对,先观察,再收紧。比 SELinux 那种上来就铁面无私要温和得多。"AppArmor 顿了顿,"不过也正因如此,有些管理员嫌麻烦,一直留在 complain 模式——那等于没有防护。"

林小源皱起眉头。光幕上那些路径规则清晰易懂,但正因为简单,反而容易被人忽视。

营地深处,林小源看到一台巨大的容器编排器正在为新容器加载 profile。

"每次容器启动,"AppArmor 跟上来,"运行时都会根据镜像的配置,把对应的 profile 注入内核。容器里的进程一睁眼,就已经被我的规则包围了。"

编排器发出低沉的嗡鸣,一条条路径规则从竹简上剥离,化作光纹印在容器的防护罩上。

"即使容器逃逸呢?"林小源问。

AppArmor 的眼神变得认真起来:"逃逸出去的进程仍然受 profile 约束。它能访问的路径不会因为逃逸而扩大。这就是纵深防御——容器隔离是一层,我的 profile 又是一层。"

林小源沉默片刻。他想起了 SELinux 的标签、capabilities 的权限位、seccomp 的系统调用白名单——每一层都不完美,但叠在一起,就成了一道难以逾越的墙。

"简单,"他低声说,"有时候也是一种力量。"

AppArmor 笑了笑,重新卷起竹简:"走吧,前面还有更深层的防御在等你。"


道藏笔记

内核启示

AppArmor 和 SELinux 走的是两条路。SELinux 给每个文件贴标签,AppArmor 直接写路径——/var/www/html/** r、/var/log/httpd/** wa,一目了然。简单是简单,但文件被移了路径规则就失效,这是它的软肋。

profile 有两种模式:enforce 真正拦截,complain 只记不拦(开发时用,先观察进程需要什么权限再写规则)。Ubuntu 默认用 AppArmor,容器安全和桌面场景也常见它的身影。上手快,维护容易,对很多场景来说够用了。

AppArmor 是路径守护——简单但实用。


破关试炼

AppArmor 之试

AppArmor 不是按 SELinux type,而是用哪种按程序路径绑定的配置约束权限?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道