容器挂载过程和安全挂载建议

绑定挂载

本文所提到的挂载主要指绑定挂载(bind mount),即通过-v /xx/xx:/xx/xx 和 --mount type=bind,xxx,xxx两种方式设置的容器挂载(其余docker卷的管理方式,如tmpfs、volume,本文暂不讨论),容器的挂载操作,简单点来讲,就是进入到容器Mount Namespace(该Namespace用于隔离挂载信息)后,调用Linux的mount()函数进行挂载。而操作系统进行(绑定)挂载的本质就是替换容器目标文件或目录inode的过程(类似硬连接,但不是),也就是使容器里面的目标文件或目录和宿主机上文件或目录使用同一个inode号(但Links不会增加)。(硬连接和bind mount的区别可见:https://www.cxyzjd.com/article/shengxia1999/52060354

容器解析挂载的过程:

docker 对于挂载操作分为三个部分:参数解析(docker-cli)、校验+设置挂载点(dockerd(moby))、执行挂载操作(runC)。

当用户从命令行输入容器挂载相关的指令后,其运行流程如下:

1.在命令行输入--mount 和 -v 参数后,docker-cli会将其进行解析存入相应的数据结构(containerConfig.HostConfig.Mounts和containerConfig.HostConfig.Binds)。

2.docker-cli将容器配置通过请求发送给dockerd进程。

(对应上图黄色处)

3.dockerd接收到 创建或运行 的请求后会完成相应的动作,但是不管创建还是运行都会对挂载点进行注册。注册时会对HostConfig.Mounts和HostConfig.Binds验证,

但只验证Mounts里面的源路径,不验证Binds的源路径,如果Mounts里面的源路径不存在则会报错。验证完成后将这两个结构体里面的值都存入container.MountPoints。

(这个新编辑器代码模块没有go,暂时用python代替)

Plain
Bash
C++
C#
CSS
Diff
HTML/XML
Java
Javascript
Markdown
PHP
Python
Ruby
SQL

// 验证HostConfig.Mounts和HostConfig.Binds时会分别调用此函数
func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, 
                                              validateBindSourceExists bool) error {
 ...
    switch mnt.Type {
    case mount.TypeBind:
  if len(mnt.Source) == 0 {
   return &errMountConfig{mnt, errMissingField("Source")}
  }
        ...
 
  if err := linuxValidateAbsolute(mnt.Source); err != nil {
   return &errMountConfig{mnt, err}
  }
 
  if validateBindSourceExists {  // 验证Mounts时该值为True,验证Binds时该值为False
   exists, _, err := p.fi.fileInfo(mnt.Source)  // <-------------验证源文件是否存在
   if err != nil {
    return &errMountConfig{mnt, err}
   }
   if !exists {  // 不存在就会报错
    return &errMountConfig{mnt, 
             errBindSourceDoesNotExist(mnt.Source)}  // 此处报错会返回errors.Errorf("bind source path does not exist: %s", path)
   }
  }
        ....
    }
    return nil
}

4.dockerd遍历container.MountPoints,又会检查每个挂载点的源路径是否存在,如果不存在就会对该路径进行逐层创建(0755权限),这里判断源路径是否存在是通过os.stat()来判断的,并不会做软链接校验,前面第3步也没做,唯一做了挂载路径软链接解析的地方是路径创建完成后,会通过defer关键字里面调用EvalSymlinks()来解析,但是此处解析后是为了设置mountLabel(是否为软链接并不影响这个label,具体见https://github.com/moby/moby/blob/6f4fbef82718212e593626419182f31a9f604316/pkg/idtools/idtools_unix.go#L25),所以,docker是允许挂载软链接的,但是这样做存在很大的风险。设置好挂载点之后dockerd会把这些挂载信息写入容器配置中。

Plain
Bash
C++
C#
CSS
Diff
HTML/XML
Java
Javascript
Markdown
PHP
Python
Ruby
SQL

func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, 
                           checkFun func(m *MountPoint) error) (path string, err error) {
 ...
    defer func() {
  ...
  var sourcePath string
  sourcePath, err = filepath.EvalSymlinks(m.Source)  // <----唯一解析挂载路径软链接的地方
  ...
  err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
  ...
 }()
    if m.Type == mounttypes.TypeBind {
        ...
 
        // 如果m.Source 存在且是一个文件就会报错,另外该函数也会保证属主正确
        // 如果逐层路径不存在,MkdirAllAndChownNew 会新建相应的路径,
        // 并且把相应的属主设置为root,如果相关路径存在则不会修改其属主和权限
  if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
   if perr, ok := err.(*os.PathError); ok {
    if perr.Err != syscall.ENOTDIR {
     return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
    }
   }
  }
 }
 return m.Source, nil
}

5.dockerd随后会通过通过调用containerd-shim运行runC。

(对应上图蓝色处)

6.runC会读取容器的配置,调用操作系统的mount函数进行挂载。

(对应上图绿色处)

具体源码解析可见:http://3ms.huawei.com/km/blogs/details/13209943

安全挂载建议:

  1. docker支持多个容器挂载相同目录,挂载方式有只读、读写两种形式。应尽量避免容器之间以读写(rw)形式挂载相同目录,否则如果某容器被黑客控制,可以更改其他容器在该目录的文件。
  2. 不要在容器上挂载敏感的主机系统目录,如/(根目录)、/boot、/etc、/dev、/lib、/proc、/sys、/usr、/var/run(或者/run)。
  3. 不要将docker.sock挂载进容器。该文件是docker客户端和服务端通信的套接字文件,挂载后会导致dockerd暴露。
  4. 确认挂载传播模式为私有模式(private或默认的rprivate),如果以共享模式挂载会对所有挂载卷进行复制,任何一个挂载卷的修改会传播到所有其它挂载卷,且此模式下不会限制其它容器挂载和对该卷进行更改。
  5. 在挂载前最好先事先保证挂载的文件存在且权限正确,并且最好使用只读挂载,即在使用-v或--volume时加上ro,使用--mount进行挂载时加上readonly。
  6. 尽量保证挂载目录隔离,即宿主机进程不会访问挂载的目录。
  7. 以最小范围原则进行挂载,即能挂载子目录就绝不挂载父目录,能挂载文件就不要挂载该文件的整个目录。
  8. 不要挂载软链接。

参考资料:

  1. http://ilearning.huawei.com/edx/next/courses-learn?courseId=100015201&articleId=18119 (张磊的课)
  2. http://isource.huawei.com/w00428594/container_security_summary/tree/master (大佬的总结)
  3. https://w3.huawei.com/ipd/tsl/#!tsl_new/standard/standard.html?standardId=44180 (公司规范)
  4. http://www.sel.zju.edu.cn/blog/2018/05/10/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3docker%E5%AE%B9%E5%99%A8%E5%BC%95%E6%93%8Erunc%E6%89%A7%E8%A1%8C%E6%A1%86%E6%9E%B6/ (runC代码解读)
  5. https://studygolang.com/articles/16906 (docker-cli以及dockerd代码解读)
  6. http://3ms.huawei.com/km/groups/2034125/home?l=zh-cn#category=8652607 (容器安全圆桌交流)

欢迎大家一起探讨、补充!

【Docker】容器使用规范--安全挂载建议的更多相关文章

  1. 运维笔记--给正在运行的Docker容器动态绑定卷组(挂载指定目录)

    场景描述: 操作系统: ubuntu16.04, docker版本: Docker version 19.03.1 系统运行一段时间后,该服务器上有一个运行中docker容器,需要在容器里边挂载本地服 ...

  2. centos:解决docker容器内挂载目录无权限 ls: cannot open directory .: Permission denied

    docker运行一个容器后,将主机中当前目录下的文件夹挂载到容器的文件夹后 进入到docker容器内对应的挂载目录中,运行命令ls后提示: ls: cannot open directory .: P ...

  3. docker 学习(七) docker 容器挂载

    1:docker的默认存放位置: $ sudo su # cd /var/lib/docker # ls -F containers/ graph/ repositories volumes/     ...

  4. Hyperledger Fabric服务器配置及修改Docker容器卷宗存储根目录/位置

    Hyperledger Fabric节点服务器对存储空间的消耗还是比较大的,在我实际生产体验的过程中,每一条请求数据大概仅2K左右,但实际占用空间远不止这点,每个节点都会对Block及链进行保存维护, ...

  5. docker挂载volume的用户权限问题,理解docker容器的uid

    docker挂载volume的用户权限问题,理解docker容器的uid 在刚开始使用docker volume挂载数据卷的时候,经常出现没有权限的问题. 这里通过遇到的问题来理解docker容器用户 ...

  6. 为什么不建议把数据库部署在Docker容器内?

    近2年Docker非常的火热,各位开发者恨不得把所有的应用.软件都部署在Docker容器中,但是您确定也要把数据库也部署的容器中吗?这个问题不是子虚乌有,因为在网上能够找到很多各种操作手册和视频教程, ...

  7. 如何修改运行中的docker容器的端口映射和挂载目录

    在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...

  8. 详解docker中容器devicemapper设备的挂载流程

    事故起因 版本说明:本文中docker版本主要基于1.10版本,操作系统为centos7.devicemapper在文中缩写为dm. 某个用户的容器启动不起来,启动时候一直报错.通过docker lo ...

  9. Docker容器挂载宿主目录的情形分析

    Docker容器启动的时候,如果要挂载宿主机的一个目录,可以用-v参数指定. 譬如我要启动一个centos容器,宿主机的/test目录挂载到容器的/soft目录,可通过以下方式指定: # docker ...

  10. windows宿主机和docker容器设置挂载共享文件夹

    docker容器内的程序经常需要访问.调用宿主机目录中的数据,每次都要导入导出非常麻烦费力. 接下来,一步步实现将宿主机的指定文件夹挂载到docker容器中. 1. 打开Oracle VM Vitua ...

随机推荐

  1. 使用IntelliJ IDEA新建一个spring boot项目

    好家伙, 使用IntelliJ IDEA新建一个spring boot项目 目的很简单,就是网页上出现一个"hello world" 别的暂时不管 首先关于工具IntelliJ I ...

  2. 华为云计算灾备产品BCManager 及eBackup的组网方式

    BCManager的作用 OceanStor BCManager是面向企业数据中心存储容灾业务的管理软件,实现容灾.双活.两地三中心等容灾环境的管理,具备多种数据库应用与虚拟化环境的容灾管理功能,简单 ...

  3. Traefik2.X 版本 中 URL Rewrite 的使用

    文章转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247484594&idx=1&sn=becbe567 ...

  4. Kibana可视化数据(Visualize)详解

    可视化 (Visualize) 功能可以为您的 Elasticsearch 数据创建可视化控件.然后,您就可以创建仪表板将这些可视化控件整合到一起展示. Kibana 可视化控件基于 Elastics ...

  5. 【原创】推流录屏软件OBS使用教程--录屏

    之前有录屏需要,写了一篇关于ffmpeg录屏的文章,反响还不错,但是直接用ffmpeg门槛有些高,今天写一篇图形界面的录屏推流工具OBS的使用教程.这次先写OBS的录屏教程 下载安装 点击 OBS官网 ...

  6. 小程序uni-app发起网络异步请求

    // uni.request({ // url: 'api/boxs/search', // // 使用监听函数防止this指向改变 // success: res => { // // 判断是 ...

  7. 关于Linux中使用bc命令计算圆周率

    Linux系统中,我们可以安装bc计算器用来计算pi值(π圆周率) 在玩的同时,这可以从某些方面反映出一个CPU的运算能力,在命令最前加上time即可 如果系统中没有bc命令,需要先进行安装:yum ...

  8. 【.NET 6+Loki+Grafana】实现轻量级日志可视化服务功能

    前言:日志功能是几乎所有程序或系统都必备的一个功能.该文章通过使用Loki+Grafana来实现日志记录与可视化查询,欢迎围观. 有关环境: 操作系统:WIN 10 .NET环境:.NET 6 开发环 ...

  9. POJ2686 Traveling by Stagecoach (状压DP)

    将车票的使用情况用二进制表示状态,对其进行转移即可. 但是我一开始写的代码是错误的(注释部分),看似思路是正确的,但是暗藏很大的问题. 枚举S,我们要求解的是dp[S][v],这个是从u转移过来的,不 ...

  10. Hbase创建表参数说明

    Hbase创建表操作及参数说明 1.创建命名空间 create_namespace 'test' 2.创建user表,列族:info create 'test:user', 'info' 3.查看表结 ...