DockerのVolumeマウントのオーナーとfixuidとeuid

なんか慣れてきた

Dockerでホスト側のディレクトリをVolumeマウントして、そこに対してコンテナの中から書き込むと、コンテナ内の実行ユーザーのuidとgidで書き込まれてしまって、ホスト側から触れない。さて、困った。ってなった。

あれー?Macのときはこんなことあったっけ?って思ってたら、Linuxの場合だけみたい。最近Dockerのそういうのばっかりひっかかってるから、なんか慣れてきた!

対応策

んで、ここにいくつか対応策が紹介されてて。そっかー。ってなった。

qiita.com

この中の最後の「実行ユーザーのuidとgidを実行時にentrypoint.shで書き換えてホスト側のものと同じにする」ってのが今の僕のやりたいことに合ってるかもなぁと思いながら・・・。

他にも面白い記事ないかなー

って探してみたら、こういう記事を見つけた。

boxboat.com

へー。コンテナの中にこのfixuidプログラムを置いて実行すれば、さっきのQiitaの記事でやってることをやってくれるっぽい。便利そう。と思いながら読んでて。

あれ?でもUSER sample:sampleみたいに実行ユーザーを指定してるのに、どうやってuidとgidを書き換えてるんだろう?と思ったので

コードを読んでみた

https://github.com/boxboat/fixuid/blob/05869a9ead4f95f7175564984a6ec52fb8ed009d/fixuid.go#L349-L379

   if err := ioutil.WriteFile("/etc/passwd", []byte(newLines), 0644); err != nil {
        return err
    }

Golang分からんけど、/etc/passwd を書き換えてるっぽい。権限なさそうなのになんで書き換えられるんだろう?と思って見てみたら、この辺。

https://github.com/boxboat/fixuid/blob/05869a9ead4f95f7175564984a6ec52fb8ed009d/fixuid.go#L48-L55

// check that script is running as root
if os.Geteuid() != 0 {
        logger.Fatalln(`fixuid is not running as root, ensure that the following criteria are met:
    - fixuid binary is owned by root: 'chown root:root /path/to/fixuid'
    - fixuid binary has the setuid bit: 'chmod u+s /path/to/fixuid'
    - NoNewPrivileges is disabled in container security profile
    - volume containing fixuid binary does not have the 'nosuid' mount option`)
}

あれ?rootで実行されてるって書いてる。

このGeteuid()ってなんだろうなー?

と思って調べたら、この記事見つけて、へーってなった。

Linuxの実効ユーザIDについて実験してみた – ブーログ

実行ファイルのオーナーがroot:rootで、パーミッションが4755だったら、一般ユーザーで実行してもroot権限で書き込めるってことなのかな。

試してみよう!

ということで、実行ファイルを作るのをGoでやってみることにした。

Go書くのはじめてだー!って思ってダウンロードしたら、既にインストールされてて、はて・・・?いつの間に?記憶にないぞ?ってなりつつバージョンアップ。

みようみまねでこんな感じのhello.goを書いた。心を込めていっこずつググりながら書いたから結構時間かかったー。

package main

import "fmt"
import "os"

func main() {
  fmt.Printf("uid: %d\n", os.Getuid())
  fmt.Printf("euid: %d\n", os.Geteuid())

  file, err := os.Create("sample.txt")
  if err != nil {
    panic(err)
  }
  defer file.Close()

  _, err = file.WriteString("Hello")
  if err != nil {
    panic(err)
  }
}

で、ビルドして実行ファイルの権限を確認。Goってお手軽だなー。

❯ go build hello.go

❯ stat --format='%a %U:%G' hello
775 bufferings:bufferings

775で、僕がオーナーになってる。

じゃ、まずは

普通のファイルには書き込めることを確認しとこう。

❯ touch sample.txt

❯ ./hello
uid: 1000
euid: 1000

❯ cat sample.txt
Hello

OKだね。

次は

権限がなかったら書き込めないことを確認。

❯ rm sample.txt

❯ sudo touch sample.txt

❯ ls -al sample.txt
-rw-r--r-- 1 root root 0  8月 26 01:33 sample.txt

❯ ./hello
uid: 1000
euid: 1000
panic: open sample.txt: permission denied

goroutine 1 [running]:
main.main()
        /path/to/project/hello.go:12 +0x1d7

OKだね。

最後に

実行ファイルのオーナーと権限を変えて実行してみよう

❯ sudo chown root:root ./hello

❯ sudo chmod 4755 ./hello

❯ stat --format='%a %U:%G' hello
4755 root:root

❯ ./hello
uid: 1000
euid: 0

❯ cat sample.txt
Hello

おー。書きこめた。こんな仕組み全然知らなかったなー。面白かった。

ところで

元々のVolumeマウントの問題は、色々考えたんだけど僕のこのマシンでしか使わないからuidとgidを1000固定でDockerfileに書いちゃうことにしようと思う。