mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-27 05:36:42 +08:00
401 lines
28 KiB
HTML
401 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
<link rel="icon" href="/static/favicon.png">
|
||
<title>19 容器安全(1):我的容器真的需要privileged权限吗.md.html</title>
|
||
<!-- Spectre.css framework -->
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
<!-- theme css & js -->
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
<div class="book-sidebar">
|
||
<div class="book-brand">
|
||
<a href="/">
|
||
<img src="/static/favicon.png">
|
||
<span>技术文章摘抄</span>
|
||
</a>
|
||
</div>
|
||
<div class="book-menu uncollapsible">
|
||
<ul class="uncollapsible">
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li><a href="../">上一级</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/00 开篇词 一个态度两个步骤,成为容器实战高手.md.html">00 开篇词 一个态度两个步骤,成为容器实战高手</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/01 认识容器:容器的基本操作和实现原理.md.html">01 认识容器:容器的基本操作和实现原理</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/02 理解进程(1):为什么我在容器中不能kill 1号进程?.md.html">02 理解进程(1):为什么我在容器中不能kill 1号进程?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/03 理解进程(2):为什么我的容器里有这么多僵尸进程?.md.html">03 理解进程(2):为什么我的容器里有这么多僵尸进程?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/04 理解进程(3):为什么我在容器中的进程被强制杀死了?.md.html">04 理解进程(3):为什么我在容器中的进程被强制杀死了?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/05 容器CPU(1):怎么限制容器的CPU使用?.md.html">05 容器CPU(1):怎么限制容器的CPU使用?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/06 容器CPU(2):如何正确地拿到容器CPU的开销?.md.html">06 容器CPU(2):如何正确地拿到容器CPU的开销?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/07 Load Average:加了CPU Cgroup限制,为什么我的容器还是很慢?.md.html">07 Load Average:加了CPU Cgroup限制,为什么我的容器还是很慢?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/08 容器内存:我的容器为什么被杀了?.md.html">08 容器内存:我的容器为什么被杀了?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/09 Page Cache:为什么我的容器内存使用量总是在临界点.md.html">09 Page Cache:为什么我的容器内存使用量总是在临界点</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/10 Swap:容器可以使用Swap空间吗?.md.html">10 Swap:容器可以使用Swap空间吗?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/11 容器文件系统:我在容器中读写文件怎么变慢了.md.html">11 容器文件系统:我在容器中读写文件怎么变慢了</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/12 容器文件Quota:容器为什么把宿主机的磁盘写满了?.md.html">12 容器文件Quota:容器为什么把宿主机的磁盘写满了?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/13 容器磁盘限速:我的容器里磁盘读写为什么不稳定.md.html">13 容器磁盘限速:我的容器里磁盘读写为什么不稳定</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/14 容器中的内存与IO:容器写文件的延时为什么波动很大?.md.html">14 容器中的内存与IO:容器写文件的延时为什么波动很大?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/15 容器网络:我修改了procsysnet下的参数,为什么在容器中不起效?.md.html">15 容器网络:我修改了procsysnet下的参数,为什么在容器中不起效?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/16 容器网络配置(1):容器网络不通了要怎么调试.md.html">16 容器网络配置(1):容器网络不通了要怎么调试</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/17 容器网络配置(2):容器网络延时要比宿主机上的高吗.md.html">17 容器网络配置(2):容器网络延时要比宿主机上的高吗</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/18 容器网络配置(3):容器中的网络乱序包怎么这么高?.md.html">18 容器网络配置(3):容器中的网络乱序包怎么这么高?</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/容器实战高手课/19 容器安全(1):我的容器真的需要privileged权限吗.md.html">19 容器安全(1):我的容器真的需要privileged权限吗</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/20 容器安全(2):在容器中,我不以root用户来运行程序可以吗?.md.html">20 容器安全(2):在容器中,我不以root用户来运行程序可以吗?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐01 案例分析:怎么解决海量IPVS规则带来的网络延时抖动问题?.md.html">加餐01 案例分析:怎么解决海量IPVS规则带来的网络延时抖动问题?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐02 理解perf:怎么用perf聚焦热点函数?.md.html">加餐02 理解perf:怎么用perf聚焦热点函数?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐03 理解ftrace(1):怎么应用ftrace查看长延时内核函数?.md.html">加餐03 理解ftrace(1):怎么应用ftrace查看长延时内核函数?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐04 理解ftrace(2):怎么理解ftrace背后的技术tracepoint和kprobe?.md.html">加餐04 理解ftrace(2):怎么理解ftrace背后的技术tracepoint和kprobe?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐05 eBPF:怎么更加深入地查看内核中的函数?.md.html">加餐05 eBPF:怎么更加深入地查看内核中的函数?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐06 BCC:入门eBPF的前端工具.md.html">加餐06 BCC:入门eBPF的前端工具</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/加餐福利 课后思考题答案合集.md.html">加餐福利 课后思考题答案合集</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/容器实战高手课/结束语 跳出舒适区,突破思考的惰性.md.html">结束语 跳出舒适区,突破思考的惰性</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
<div class="sidebar-toggle-inner"></div>
|
||
</div>
|
||
<script>
|
||
function add_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.add('show')
|
||
}
|
||
function remove_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.remove('show')
|
||
}
|
||
function sidebar_toggle() {
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let content = document.querySelector('.off-canvas-content')
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
sidebar_toggle.classList.remove('extend')
|
||
sidebar.classList.remove('hide')
|
||
content.classList.remove('extend')
|
||
} else { // hide
|
||
sidebar_toggle.classList.add('extend')
|
||
sidebar.classList.add('hide')
|
||
content.classList.add('extend')
|
||
}
|
||
}
|
||
function open_sidebar() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.add('show')
|
||
overlay.classList.add('show')
|
||
}
|
||
function hide_canvas() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.remove('show')
|
||
overlay.classList.remove('show')
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
<div class="columns">
|
||
<div class="column col-12 col-lg-12">
|
||
<div class="book-navbar">
|
||
<!-- For Responsive Layout -->
|
||
<header class="navbar">
|
||
<section class="navbar-section">
|
||
<a onclick="open_sidebar()">
|
||
<i class="icon icon-menu"></i>
|
||
</a>
|
||
</section>
|
||
</header>
|
||
</div>
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;">
|
||
<div class="book-post">
|
||
<p id="tip" align="center"></p>
|
||
<div><h1>19 容器安全(1):我的容器真的需要privileged权限吗</h1>
|
||
<p>你好,我是程远。从今天这一讲,我们进入到了容器安全的模块。</p>
|
||
<p>容器安全是一个很大的话题,容器的安全性很大程度是由容器的架构特性所决定的。比如容器与宿主机共享 Linux 内核,通过 Namespace 来做资源的隔离,通过 shim/runC 的方式来启动等等。</p>
|
||
<p>这些容器架构特性,在你选择使用容器之后,作为使用容器的用户,其实你已经没有多少能力去对架构这个层面做安全上的改动了。你可能会说用Kata Container、gVisor 就是安全“容器”了。不过,Kata 或者 gVisor 只是兼容了容器接口标准,而内部的实现完全是另外的技术了。</p>
|
||
<p>那么对于使用容器的用户,在运行容器的时候,在安全方面可以做些什么呢?我们主要可以从这两个方面来考虑:第一是赋予容器合理的 capabilities,第二是在容器中以非 root 用户来运行程序。</p>
|
||
<p>为什么是这两点呢?我通过两讲的内容和你讨论一下,这一讲我们先来看容器的 capabilities 的问题。</p>
|
||
<h2>问题再现</h2>
|
||
<p>刚刚使用容器的同学,往往会发现用缺省 docker run的方式启动容器后,在容器里很多操作都是不允许的,即使是以 root 用户来运行程序也不行。</p>
|
||
<p>我们用下面的例子来重现一下这个问题。我们先运行make image 做个容器镜像,然后运行下面的脚本:</p>
|
||
<pre><code># docker run --name iptables -it registry/iptables:v1 bash
|
||
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8aab7b7ac98e8bae0e0bceeece0eee9ece1">[email protected]</a> /]# iptables -L
|
||
iptables v1.8.4 (nf_tables): Could not fetch rule set generation id: Permission denied (you must be root)
|
||
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3a4855554e7a0a5802025e0c0e020c0b0e03">[email protected]</a> /]# id
|
||
uid=0(root) gid=0(root) groups=0(root)
|
||
</code></pre>
|
||
<p>在这里,我们想在容器中运行 iptables 这个命令,来查看一下防火墙的规则,但是执行命令之后,你会发现结果输出中给出了"Permission denied (you must be root)"的错误提示,这个提示要求我们用 root 用户来运行。</p>
|
||
<p>不过在容器中,我们现在已经是以 root 用户来运行了,么为什么还是不可以运行"iptables"这条命令呢?</p>
|
||
<p>你肯定会想到,是不是容器中又做了别的权限限制?如果你去查一下资料,就会看到启动容器有一个"privileged"的参数。我们可以试一下用上这个参数,没错,我们用了这个参数之后,iptables 这个命令就执行成功了。</p>
|
||
<pre><code># docker stop iptables;docker rm iptables
|
||
iptables
|
||
iptables
|
||
# docker run --name iptables --privileged -it registry/iptables:v1 bash
|
||
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="067469697246323237303e6032643f643432">[email protected]</a> /]# iptables -L
|
||
Chain INPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
|
||
Chain FORWARD (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain OUTPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
</code></pre>
|
||
<p>看上去,我们用了一个配置参数就已经解决了问题,似乎很容易。不过这里我们可以进一步想想,用"privileged"参数来解决问题,是不是一个合理的方法呢?用它会有什么问题吗?</p>
|
||
<p>要回答这些问题,我们先来了解一下"privileged"是什么意思。从 Docker 的代码里,我们可以看到,如果配置了 privileged 的参数的话,就会获取所有的 capabilities,那什么是 capabilities 呢?</p>
|
||
<pre><code> if ec.Privileged {
|
||
p.Capabilities = caps.GetAllCapabilities()
|
||
}
|
||
</code></pre>
|
||
<h2>基本概念</h2>
|
||
<h3>Linux capabilities</h3>
|
||
<p>要了解 Linux capabilities 的定义,我们可以先查看一下"Linux Programmer's Manual"中关于Linux capabilities的描述。</p>
|
||
<p>在 Linux capabilities 出现前,进程的权限可以简单分为两类,第一类是特权用户的进程(进程的有效用户 ID 是 0,简单来说,你可以认为它就是 root 用户的进程),第二类是非特权用户的进程(进程的有效用户 ID 是非 0,可以理解为非 root 用户进程)。</p>
|
||
<p>特权用户进程可以执行 Linux 系统上的所有操作,而非特权用户在执行某些操作的时候就会被内核限制执行。其实这个概念,也是我们通常对 Linux 中 root 用户与非 root 用户的理解。</p>
|
||
<p>从 kernel 2.2 开始,Linux 把特权用户所有的这些“特权”做了更详细的划分,这样被划分出来的每个单元就被称为 capability。</p>
|
||
<p>所有的 capabilities 都在Linux capabilities的手册列出来了,你也可以在内核的文件capability.h中看到所有 capabilities 的定义。</p>
|
||
<p>对于任意一个进程,在做任意一个特权操作的时候,都需要有这个特权操作对应的 capability。</p>
|
||
<p>比如说,运行 iptables 命令,对应的进程需要有 CAP_NET_ADMIN 这个 capability。如果要 mount 一个文件系统,那么对应的进程需要有 CAP_SYS_ADMIN 这个 capability。</p>
|
||
<p>我还要提醒你的是,CAP_SYS_ADMIN 这个 capability 里允许了大量的特权操作,包括文件系统,交换空间,还有对各种设备的操作,以及系统调试相关的调用等等。</p>
|
||
<p>在普通 Linux 节点上,非 root 用户启动的进程缺省没有任何 Linux capabilities,而 root 用户启动的进程缺省包含了所有的 Linux capabilities。</p>
|
||
<p>我们可以做个试验,对于 root 用户启动的进程,如果把 CAP_NET_ADMIN 这个 capability 移除,看看它是否还可以运行 iptables。</p>
|
||
<p>在这里我们要用到capsh这个工具,对这个工具不熟悉的同学可以查看超链接。接下来,我们就用 capsh 执行下面的这个命令:</p>
|
||
<pre><code># sudo /usr/sbin/capsh --keep=1 --user=root --drop=cap_net_admin -- -c './iptables -L;sleep 100'
|
||
Chain INPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain FORWARD (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain OUTPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
iptables: Permission denied (you must be root).
|
||
</code></pre>
|
||
<p>这时候,我们可以看到即使是 root 用户,如果把"CAP_NET_ADMIN"给移除了,那么在执行 iptables 的时候就会看到"Permission denied (you must be root)."的提示信息。</p>
|
||
<p>同时,我们可以通过 /proc 文件系统找到对应进程的 status,这样就能确认进程中的 CAP_NET_ADMIN 是否已经被移除了。</p>
|
||
<pre><code># ps -ef | grep sleep
|
||
root 22603 22275 0 19:44 pts/1 00:00:00 sudo /usr/sbin/capsh --keep=1 --user=root --drop=cap_net_admin -- -c ./iptables -L;sleep 100
|
||
root 22604 22603 0 19:44 pts/1 00:00:00 /bin/bash -c ./iptables -L;sleep 100
|
||
# cat /proc/22604/status | grep Cap
|
||
CapInh: 0000000000000000
|
||
CapPrm: 0000003fffffefff
|
||
CapEff: 0000003fffffefff
|
||
CapBnd: 0000003fffffefff
|
||
CapAmb: 0000000000000000
|
||
</code></pre>
|
||
<p>运行上面的命令查看 /proc//status 里 Linux capabilities 的相关参数之后,我们可以发现,输出结果中包含 5 个 Cap 参数。</p>
|
||
<p>这里我给你解释一下, 对于当前进程,直接影响某个特权操作是否可以被执行的参数,是"CapEff",也就是"Effective capability sets",这是一个 bitmap,每一个 bit 代表一项 capability 是否被打开。</p>
|
||
<p>在 Linux 内核capability.h里把 CAP_NET_ADMIN 的值定义成 12,所以我们可以看到"CapEff"的值是"0000003fffffefff",第 4 个数值是 16 进制的"e",而不是 f。</p>
|
||
<p>这表示 CAP_NET_ADMIN 对应的第 12-bit 没有被置位了(0xefff = 0xffff & (~(1 << 12))),所以这个进程也就没有执行 iptables 命令的权限了。</p>
|
||
<p>对于进程 status 中其他几个 capabilities 相关的参数,它们还需要和应用程序文件属性中的 capabilities 协同工作,这样才能得到新启动的进程最终的 capabilities 参数的值。</p>
|
||
<p>我们看下面的图,结合这张图看后面的讲解:</p>
|
||
<p><img src="assets/906a996776f84d8f856cc7f62589095c.jpeg" alt="img" /></p>
|
||
<p>如果我们要新启动一个程序,在 Linux 里的过程就是先通过 fork() 来创建出一个子进程,然后调用 execve() 系统调用读取文件系统里的程序文件,把程序文件加载到进程的代码段中开始运行。</p>
|
||
<p>就像图片所描绘的那样,这个新运行的进程里的相关 capabilities 参数的值,是由它的父进程以及程序文件中的 capabilities 参数值计算得来的。</p>
|
||
<p>具体的计算过程你可以看Linux capabilities的手册中的描述,也可以读一下网上的这两篇文章:</p>
|
||
<p>Capabilities: Why They Exist and How They Work</p>
|
||
<p>Linux Capabilities in Practice</p>
|
||
<p>我就不对所有的进程和文件的 capabilities 集合参数和算法挨个做解释了,感兴趣的话你可以自己详细去看看。</p>
|
||
<p>这里你只要记住最重要的一点,文件中可以设置 capabilities 参数值,并且这个值会影响到最后运行它的进程。比如,我们如果把 iptables 的应用程序加上 CAP_NET_ADMIN 的 capability,那么即使是非 root 用户也有执行 iptables 的权限了。</p>
|
||
<pre><code>$ id
|
||
uid=1000(centos) gid=1000(centos) groups=1000(centos),10(wheel)
|
||
$ sudo setcap cap_net_admin+ep ./iptables
|
||
$ getcap ./iptables
|
||
./iptables = cap_net_admin+ep
|
||
$./iptables -L
|
||
Chain INPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain FORWARD (policy ACCEPT)
|
||
target prot opt source destination
|
||
DOCKER-USER all -- anywhere anywhere
|
||
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
|
||
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
|
||
DOCKER all -- anywhere anywhere
|
||
ACCEPT all -- anywhere anywhere
|
||
ACCEPT all -- anywhere anywhere
|
||
…
|
||
</code></pre>
|
||
<p>好了,关于 Linux capabilities 的内容到这里我们就讲完了,其实它就是把 Linux root 用户原来所有的特权做了细化,可以更加细粒度地给进程赋予不同权限。</p>
|
||
<h2>解决问题</h2>
|
||
<p>我们搞懂了 Linux capabilities 之后,那么对 privileged 的容器也很容易理解了。Privileged 的容器也就是允许容器中的进程可以执行所有的特权操作。</p>
|
||
<p>因为安全方面的考虑,容器缺省启动的时候,哪怕是容器中 root 用户的进程,系统也只允许了 15 个 capabilities。这个你可以查看runC spec 文档中的 security 部分,你也可以查看容器 init 进程 status 里的 Cap 参数,看一下容器中缺省的 capabilities。</p>
|
||
<pre><code># docker run --name iptables -it registry/iptables:v1 bash
|
||
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84f6ebebf0c4e1b1b0b2bdb0b2b1b6e5b0b6">[email protected]</a> /]# cat /proc/1/status |grep Cap
|
||
CapInh: 00000000a80425fb
|
||
CapPrm: 00000000a80425fb
|
||
CapEff: 00000000a80425fb
|
||
CapBnd: 00000000a80425fb
|
||
CapAmb: 0000000000000000
|
||
</code></pre>
|
||
<p>我想提醒你,当我们发现容器中运行某个程序的权限不够的时候,并不能“偷懒”把容器设置为"privileged",也就是把所有的 capabilities 都赋予了容器。</p>
|
||
<p>因为容器中的权限越高,对系统安全的威胁显然也是越大的。比如说,如果容器中的进程有了 CAP_SYS_ADMIN 的特权之后,那么这些进程就可以在容器里直接访问磁盘设备,直接可以读取或者修改宿主机上的所有文件了。</p>
|
||
<p>所以,在容器平台上是基本不允许把容器直接设置为"privileged"的,我们需要根据容器中进程需要的最少特权来赋予 capabilities。</p>
|
||
<p>我们结合这一讲开始的例子来说说。在开头的例子中,容器里需要使用 iptables。因为使用 iptables 命令,只需要设置 CAP_NET_ADMIN 这个 capability 就行。那么我们只要在运行 Docker 的时候,给这个容器再多加一个 NET_ADMIN 参数就可以了。</p>
|
||
<pre><code># docker run --name iptables --cap-add NET_ADMIN -it registry/iptables:v1 bash
|
||
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b0c2dfdfc4f0d3d6d5d4d6818284d4d3d681">[email protected]</a> /]# iptables -L
|
||
Chain INPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain FORWARD (policy ACCEPT)
|
||
target prot opt source destination
|
||
Chain OUTPUT (policy ACCEPT)
|
||
target prot opt source destination
|
||
</code></pre>
|
||
<h2>重点小结</h2>
|
||
<p>这一讲我们主要学习了如何给容器赋予合理的 capabilities。</p>
|
||
<p>那么,我们自然需要先来理解什么是 Linux capabilities。其实 Linux capabilities 就是把 Linux root 用户原来所有的特权做了细化,可以更加细粒度地给进程赋予不同权限。</p>
|
||
<p>对于 Linux 中的每一个特权操作都有一个对应的 capability,对于一个 capability,有的对应一个特权操作,有的可以对应很多个特权操作。</p>
|
||
<p>每个 Linux 进程有 5 个 capabilities 集合参数,其中 Effective 集合里的 capabilities 决定了当前进程可以做哪些特权操作,而其他集合参数会和应用程序文件的 capabilities 集合参数一起来决定新启动程序的 capabilities 集合参数。</p>
|
||
<p>对于容器的 root 用户,缺省只赋予了 15 个 capabilities。如果我们发现容器中进程的权限不够,就需要分析它需要的最小 capabilities 集合,而不是直接赋予容器"privileged"。</p>
|
||
<p>因为"privileged"包含了所有的 Linux capabilities, 这样"privileged"就可以轻易获取宿主机上的所有资源,这会对宿主机的安全产生威胁。所以,我们要根据容器中进程需要的最少特权来赋予 capabilities。</p>
|
||
<h2>思考题</h2>
|
||
<p>你可以查看一下你的 Linux 系统里 ping 程序文件有哪些 capabilities,看看有什么办法,能让 Linux 普通用户没有执行 ping 的能力。</p>
|
||
<p>欢迎你在留言区和我交流互动。如果学完这一讲让你有所收获,也欢迎转发给你的同事、或者朋友,一起交流探讨容器安全的问题。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/容器实战高手课/18 容器网络配置(3):容器中的网络乱序包怎么这么高?.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/容器实战高手课/20 容器安全(2):在容器中,我不以root用户来运行程序可以吗?.md.html">下一页</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
</div>
|
||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709977aa6f813cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
</body>
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
dataLayer.push(arguments);
|
||
}
|
||
gtag('js', new Date());
|
||
gtag('config', 'G-NPSEEVD756');
|
||
var path = window.location.pathname
|
||
var cookie = getCookie("lastPath");
|
||
console.log(path)
|
||
if (path.replace("/", "") === "") {
|
||
if (cookie.replace("/", "") !== "") {
|
||
console.log(cookie)
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
}
|
||
} else {
|
||
setCookie("lastPath", path)
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
var d = new Date();
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
var expires = "expires=" + d.toGMTString();
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
}
|
||
function getCookie(cname) {
|
||
var name = cname + "=";
|
||
var ca = document.cookie.split(';');
|
||
for (var i = 0; i < ca.length; i++) {
|
||
var c = ca[i].trim();
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
}
|
||
return "";
|
||
}
|
||
</script>
|
||
</html>
|