- 投稿日:2019-12-24T22:56:48+09:00
Linux Network NamespaceをGoで操作する
TL;DR
- Go言語のgoroutineはdefaultではpreemptiveに動作するOS Threadが切り替わるのでOS Threadに強く紐づくlinuxのnamespace関連の操作を行うときは
runtime.LockOSThread()
しておく必要がある。1- Go言語でLinuxのnetwork namespaceを操作したい場合はCNIのライブラリを使うのが便利
なんでこんな事してるの?
テナント(200~)毎にVMを用意してると管理やコストが大きいため、アドレス空間が衝突してるテナントに対してHTTP(S)リバースプロキシを提供する仕組みを作ってみようと思った。
Proof of Concept
試しに下記のコードを実行してみる。
package main import ( "log" "net" "net/http" "os" "runtime" "github.com/containernetworking/plugins/pkg/ns" ) func main() { nspath := os.Args[1] addr := os.Args[2] var err error var l net.Listener ns.WithNetNSPath(nspath, func(_ ns.NetNS) error { l, err = net.Listen("tcp", addr) return nil }) runtime.UnlockOSThread() if err != nil { log.Fatal(err) } if err := http.Serve(l, nil); err != nil { log.Fatal(err) } }このコード動かすには下記の様にネットワーク的に隔離されたコンテナを用意しておくとよい。
# build binary go build -o nsproxy nsproxy.go # setup environment docker run -d --net none --name pause k8s.gcr.io/pause:3.1 ns=$(docker inspect --format '{{ .NetworkSettings.SandboxKey }}' pause) # run program sudo ./nsproxy "$ns" 127.0.0.1:8080 &このバイナリを動かした場合、HTTPサーバーとして動作しているタイミングではコンテナのnetwork namaspace(以後netnsと表記)には存在していない。
# ls -l /proc/1/ns/net # hostの初期netnsの情報 lrwxrwxrwx 1 root root 0 Dec 24 21:42 /proc/1/ns/net -> 'net:[4026531984]' # ls -l /proc/$(pgrep nsproxy)/task/*/ns/net # nsproxyプロセスはホストのnetnsに居る lrwxrwxrwx 1 root root 0 Dec 24 21:42 /proc/4377/task/4377/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4378/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4379/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4380/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4381/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4382/ns/net -> 'net:[4026531984]' lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4393/ns/net -> 'net:[4026531984]' # ls -l /proc/$(docker inspect --format '{{.State.Pid}}' pause)/task/*/ns/net # containerのnetnsの情報 lrwxrwxrwx 1 root root 0 Dec 24 21:50 /proc/3867/task/3867/ns/net -> 'net:[4026532117]'しかしながらnsenterを用いてコンテナのnetnsの中に入ると
127.0.0.1:8080
でhttpサーバーが動作していることが分かる。# nsenter --net=$(docker inspect --format '{{ .NetworkSettings.SandboxKey }}' pause) bash # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever # ss -ltn State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* # curl http://127.0.0.1:8080 -v * Expire in 0 ms for 6 (transfer 0x5627619e7f50) * Trying 127.0.0.1... * TCP_NODELAY set * Expire in 200 ms for 4 (transfer 0x5627619e7f50) * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.64.0 > Accept: */* > < HTTP/1.1 404 Not Found < Content-Type: text/plain; charset=utf-8 < X-Content-Type-Options: nosniff < Date: Tue, 24 Dec 2019 12:58:10 GMT < Content-Length: 19 < 404 page not found * Connection #0 to host 127.0.0.1 left intactたくさんのコンテナからアクセスできるようにしてみる
この方法がどれだけスケールすのか試してみる。
Listenするポートを複数になるように拡張する。package main import ( "log" "net" "net/http" "os" "runtime" "sync" "github.com/containernetworking/plugins/pkg/ns" ) func main() { addr := os.Args[1] var ls []net.Listener for _, nspath := range os.Args[2:] { ns.WithNetNSPath(nspath, func(_ ns.NetNS) error { l, err := net.Listen("tcp", addr) if err != nil { log.Fatal(err) } ls = append(ls, l) return nil }) } runtime.UnlockOSThread() var wg sync.WaitGroup for _, l := range ls { wg.Add(1) go func(l net.Listener){ err := http.Serve(l, nil) if err != nil { log.Print(err) } wg.Done() }(l) } wg.Wait() }下記の様に100個ほどコンテナを用意する
# 100個のコンテナを作成する seq 1000 1999 | xargs -I '{}' -exec docker run -d --net none --name 'pause{}' k8s.gcr.io/pause:3.1 # 100個のコンテナに対してListenする sudo ./nsproxy 127.0.0.1:8080 $(docker inspect --format '{{.NetworkSettings.SandboxKey}}' pause{100..199} ) &プロセスの稼働開始直後の状態
$ sudo cat /proc/$(pgrep nsproxy)/status Name: nsproxy Umask: 0022 State: S (sleeping) Tgid: 17082 Ngid: 0 Pid: 17082 PPid: 17068 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 128 Groups: 0 NStgid: 17082 NSpid: 17082 NSpgid: 17068 NSsid: 3567 VmPeak: 618548 kB VmSize: 561720 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 10980 kB VmRSS: 10980 kB RssAnon: 6608 kB RssFile: 4372 kB RssShmem: 0 kB VmData: 161968 kB VmStk: 140 kB VmExe: 2444 kB VmLib: 1500 kB VmPTE: 140 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 Threads: 7 SigQ: 0/15453 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000000000 SigCgt: ffffffffffc1feff CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff Cpus_allowed_list: 0-239 Mems_allowed: 00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 6 nonvoluntary_ctxt_switches: 0開始直後ではRSSが10980 kB程度とかなり軽量であることが分かる。
まとめ
network namespaceを触るのは怖くないので皆さんも触ってみてください。CNIのライブラリ自体は軽量なのでぜひとも実装自体を覗いてみてください。
- 投稿日:2019-12-24T22:23:27+09:00
Linuxコマンド一覧
目的
12月から運よく業務でデータ解析に携わることになりました。
ただ、伴って、Linuxコマンドを使用する機会も増えてきましたので、復習用にまとめてみました。
ドットインストール様の講座を参考にさせて頂いております。
これが無料とは信じられませんが...参照動画
https://dotinstall.com/lessons/basic_unix_v2
環境
ローカル開発環境のCentOS上を利用
↓ 環境立ち上げ方法
https://dotinstall.com/lessons/basic_localdev_win_v2*versionが最新版だと上手く立ち上がらない可能性があります。
コマンド一覧
pwd:現在のディレクトリを確認
[vagrant@localhost ~]$ pwd /home/vagrantclear, Ctrl + l:画面のリセット
cd:ディレクトリ移動
cd [ディレクトリ名]:[ディレクトリ名]に移動
[vagrant@localhost ~]$ pwd /home/vagrant [vagrant@localhost ~]$ cd unix_lessons/ [vagrant@localhost unix_lessons]$ pwd /home/vagrant/unix_lessonscd ..:一つ上のディレクトリへ移動
[vagrant@localhost unix_lessons]$ pwd /home/vagrant/unix_lessons [vagrant@localhost unix_lessons]$ cd .. [vagrant@localhost ~]$ pwd /home/vagrantcd -:直前のディレクトリに戻る
[vagrant@localhost unix_lessons]$ pwd /home/vagrant/unix_lessons [vagrant@localhost unix_lessons]$ cd .. [vagrant@localhost ~]$ pwd /home/vagrant [vagrant@localhost ~]$ cd - /home/vagrant/unix_lessons [vagrant@localhost unix_lessons]$ pwd /home/vagrant/unix_lessonscd !$:直前のコマンドに渡した最後の文字列を引用してファイル移動
[vagrant@localhost unix_lessons]$ ls myapp/ hello.txt [vagrant@localhost unix_lessons]$ cd !$ cd myapp/ [vagrant@localhost myapp]$myappへ移動成功
ls:下位ファイルの確認
mkdir:ディレクトリの作成
[vagrant@localhost unix_lessons]$ mkdir myapp [vagrant@localhost unix_lessons]$ ls myappcp :ファイルのコピー
cp [コピーするファイル] [コピー先]
同じディレクトリ内にmyappをmyapp2の名前でコピー
[vagrant@localhost unix_lessons]$ cp -r myapp myapp2 [vagrant@localhost unix_lessons]$ ls myapp myapp2*cp [コピーするファイル] . で同じディレクトリにコピー
mv:ファイルの移動
mv [移動するファイル] [移動先]
myapp3ファイルをmyapp2下位に移動させる
*mkdir -p myapp3/configの「-p」は、app3ファイルがない状態でconfigファイルを作成するのに必要。[vagrant@localhost unix_lessons]$ ls myapp myapp2 [vagrant@localhost unix_lessons]$ mkdir -p myapp3/config [vagrant@localhost unix_lessons]$ ls myapp myapp2 myapp3 [vagrant@localhost unix_lessons]$ ls myapp3 config [vagrant@localhost unix_lessons]$ mv myapp3 myapp2 [vagrant@localhost unix_lessons]$ ls myapp2 myapp3rmdir:ファイルの削除
rmdir [削除したいファイル名]
*ただし、削除できるファイルは空のみ[vagrant@localhost unix_lessons]$ ls myapp3 config [vagrant@localhost unix_lessons]$ rmdir myapp2/myapp3/config [vagrant@localhost unix_lessons]$ ls myapp2/myapp3*中身があるファイルを削除する場合
[vagrant@localhost unix_lessons]$ rmdir myapp2 rmdir: failed to remove `myapp2': ディレクトリは空ではありません [vagrant@localhost unix_lessons]$ rm -r myapp2 [vagrant@localhost unix_lessons]$ ls myappcat:ファイルの中身確認
[vagrant@localhost unix_lessons]$ cat ./myapp/hello.txtless:ファイルの中身確認
catと使い方が異なる
・矢印:スクロール
・Space/ Ctrl+F 一画面先
・Ctrl+B 一画面前
・g 先頭へ移動
・Shift+g 末尾へ移動
・q 終了
・/検索語Ctrl+c:キャンセル
Ctrl+r コマンドの履歴を検索
history:過去のコマンド一覧を表示
一覧中のコマンドを実行したければ、 ![任意の数字]で実行できる。
! 直前のコマンドを色々引用できる
[vagrant@localhost unix_lessons]$ !pw ←直近でpwから始まるコマンドを実行 pwd /home/vagrant/unix_lessons [vagrant@localhost unix_lessons]$ !pw:p ←あやふやなら :pで表示だけも可 pwd [vagrant@localhost unix_lessons]$ !! ←実行 pwd /home/vagrant/unix_lessonshelp, man:コマンドのhelp表示
[vagrant@localhost unix_lessons]$ mkdir --help Usage: mkdir [OPTION]... DIRECTORY... ディレクトリを作成する。ただし既にディレクトリがあれば何もしない。 長いオプションに必須の引数は短いオプションにも必須です. -m, --mode=MODE set file mode (as in chmod), not a=rwx - umask -p, --parents no error if existing, make parent directories as needed -v, --verbose print a message for each created directory -Z, --context=CTX set the SELinux security context of each created directory to CTX When COREUTILS_CHILD_DEFAULT_ACLS environment variable is set, -p/--parents option respects default umask and ACLs, as it does in Red Hat Enterprise Linux 7 by default --help この使い方を表示して終了 --version バージョン情報を表示して終了 Report mkdir bugs to bug-coreutils@gnu.org GNU coreutils home page: <http://www.gnu.org/software/coreutils/> General help using GNU software: <http://www.gnu.org/gethelp/> Report mkdir translation bugs to <http://translationproject.org/team/> For complete documentation, run: info coreutils 'mkdir invocation'*man [コマンド]でもっと詳しい表示ができる。less表示だが...
vim
vi [ファイル名]でvimを起動
vimにはコマンドモードと編集モードがある。左下に「INSERT」表示があれば編集モード。
〇コマンドモード ⇒ 編集モード:i
〇編集モード ⇒ コマンドモード:Esc終了・保存する場合はコマンドモードで、
:w 保存
:q 終了
:q! 変更内容を破棄して終了環境立ち上げ
〇Windows PowerShellで仮想マシンを立ち上げる
PS C:\Users\takuy> cd MyVagrant/MyCentOS PS C:\Users\takuy\MyVagrant\MyCentOS> vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Checking if box 'bento/centos-6.8' version '2.3.4' is up to date... ==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision` ==> default: flag to force provisioning. Provisioners marked to run always will still run. PS C:\Users\takuy\MyVagrant\MyCentOS> vagrant status Current machine states: default running (virtualbox) The VM is running. To stop this VM, you can run `vagrant halt` to shut it down forcefully, or you can run `vagrant suspend` to simply suspend the virtual machine. In either case, to restart it again, simply run `vagrant up`.〇PuTTyで作成したMyCentOSをloadする
ユーザー名とpassはvagrant終了するとき
〇PuTTy上でexitコマンド
〇Powershell上で仮想マシンを停止させる
PS C:\Users\takuy\MyVagrant\MyCentOS> vagrant suspend ==> default: Saving VM state and suspending execution...
- 投稿日:2019-12-24T20:47:33+09:00
Ubuntu最高
昔はインフラエンジニアでした
LinuxよりUnixのほうが多かったかな
昔はインフラエンジニアでした。
実はHP-UX・AIX・Solaris・DegitalUnixなんて使ってました。
殆どがSparkStationとHPの端末からでしたけど。
その頃はある携帯キャリアの決済システムやWebシステム・メールシステム・メールストアの管理だったんですが。
色んな事やってました。
監視室の設営とかもやってましたね。
若い会社だったので楽しかったですよ。
そのころはLinux使ってみようかなと思ってましたがパーティション壊すのが怖くて使ってませんでした。Linuxを組むようになったのは29歳頃から
Linuxでシステム組むようになったのは29歳ぐらいのときから組んでました。
それまではFD起動で遊んでました。
遊びが好して仕事になってました。
でも困ったことが一つなぜか知らないけどサーバ管理以外の仕事も引き受けさせられ地獄でした。Linux系の通販のバックオフィスの会社に入りました。
その時もLinuxを楽しんでました。
どんな組み方できるか日々VMを潰しながら研究したりLinuxインストールしたりして勉強と研究に没頭してました。
たまには東京にサーバ設置という名の出張もしていましたね。
あのときも楽しかったんですがクラッカーにやられたことがあって大変な目にあったことがあります。
夜半になっても原因がわからず最終的には◯国系クラッカーが攻撃していたとのこと怖かったです。その会社をやめた理由は実はプログラマーになりたかった。
プログラマーにはなりたかったんですがインフラが楽しすぎたんですね。
そろそろプログラミングがしたいなと思い楽しかったのですがその会社をやめ通販会社に行きました。
ですが、Access使ったシステムだったのです。
何も教えていただけませんでした。
それからというものLinuxは遊びで使うものと考えていました。
CentOSばかり使ってましたね。
そのころ。岐阜県のスマホ人材育成生になりました。
それぐらいからですかね。
ちょっとWindowsが嫌になってきたのはついに師匠からMacBookWhiteを譲っていただきました。
それからというものWindowsよりMacOSやLinuxのほうがいいやと思っていました。ついにであってしまった。
Ubuntuに出会いました。
CentOSって意外と設定がめんどいのとチューニングがめんどくさいのでいいのないかなと探していたら見つけちゃいました。
Ubuntuくんを。
それからと言っていいものAspireOneにUbuntu突っ込んだりMacにVirtualbox入れてUbuntu入れたりしています。
WSLも実はUbuntuです。
それだけUbuntuが好きなのにはやはりチューニングのしやすさにありました。
それだし起動は早いし。
最速インストールが必要でない限りはUbuntuですね。
やっぱ新しいものも早く手に入るし楽しいもん。Ubuntu談義になってしまいすいませんでした。
みなさんもLinuxライフ楽しんでください。
- 投稿日:2019-12-24T19:48:35+09:00
Linuxの不揮発メモリ(NVDIMM対応について(2019年版 補足編)
はじめに
(この記事はFujitsu Advent Calendar 2019 24日目の記事です。なお、記事は個人の見解であり、組織を代表するものではありません。)
というわけで、今年もNVDIMMこと不揮発メモリの話を書くことにしました。補足編となっているのは、今年はすでに12月の講演で最新状況をほとんど話してしまっていて、その講演の時間内に収められなかった小ネタを補足としてするつもりだからです。
正直言うと、2019年は業務の都合上NVDIMMのお仕事はほとんど何もやっていなかったんです。しかし、過去3回にわたってひたすらNVDIMMの話を書いているせいか、最近はすっかりNVDIMMの人として見られるようになっている気がします。そのおかげで過去の記事をご覧になった大学の先生などから講演のお声がかかるようになってきたというわけです。ありがたいことです。 Outputを外に出すとそれが自分にいい方向で跳ね返ってくるというのを身をもって体験していて、中々感慨深いものがあります。
- 2019/3月 Big Data Infra研究会で講演
- 2019/12月 情報処理学会のComsys 2019にて講演
以下は、前者が3月の時の資料、後者が12月の情報処理学会 Comsys 2019の資料です。12月の時は後半の最新状況が更新されています。
不揮発メモリ(NVDIMM)とLinuxの対応動向について
不揮発メモリ(NVDIMM)とLinuxの対応動向について (for comsys 2019 ver.)というわけで、今年の最新動向についてはこの記事より前に、まずは上記(特に12月の方の資料)をご参照ください。
ここでは、12月の講演の45分の時間中に入りきらなかった話や気が付いてなかった点について補足しようと思います。なお不揮発メモリについては、今年はNAOMASA MATSUBAYASHIさんによる解説資料
電源を切っても消えないメモリとの付き合い方
が資料も公開されています。PMDKのlibpmemobjの使い方などは、私の解説より詳しいのでこちらも参照するとよいでしょう。1.不揮発メモリを揮発メモリとして使うカーネルの機能
Dave Hansenさんによって、NVDIMMを容量の大きなRAMとして使うためのkernelの機能が開発されました。
https://lkml.org/lkml/2019/1/24/1346NVDIMMにはもともと癖のある不揮発な性質よりも、不揮発メモリの大容量性のみに着目して従来のRAMと同じように不揮発メモリを使いたいという期待もありました。このため、Intelはこれまで以下の方法を提供してきました。
A) Memory Mode
ハード/ファームの設定により、RAMをcacheにしてNVDIMMの領域を通常のMemory領域に見せるというModeです。(これに対して、NVDIMMを本当に不揮発メモリに見せるModeはApp Direct Modeと呼びます)。
Memory Modeではソフトの変更を一切行わずに済むのが長所です。しかし、RAMをキャッシュとして使ってしまうので、RAMの容量はメモリサイズとしては見えなくなってしまい、図のようにNVDIMMの容量がそのままメモリサイズとなります。また、キャッシュであるのでキャッシュミスしたときのレイテンシーの変化などはソフト側で予測できず、またコントロールできないのも欠点です。
B) libraryによってNVDIMMの揮発メモリのように使う
(元)PMDKのlibvmemのように、ライブラリレベルでDAXとしてわりあてたNVDIMMの領域を揮発メモリ的に使う方法です。当然、これらのライブラリを使わないと揮発メモリとしては使えないので、ミドルウェアやアプリがこのライブラリに対応している必要があります。このため、これを使うのはちょっと面倒です。
そこで登場したのがDaveさんのパッチです。
C) kernelレイヤでNVDIMMを揮発メモリとして見せてくれる
これによると、Device DAXとしてフォーマットした領域をいったん切り離して、それをMemory hotaddしてRAMとして見せるようにしているようです。これによりNVDIMMがユーザからは少し遅いRAMとして見えるようになります。
この方式の長所は以下があげられます。
* Memory Modeと違って、RAMとNVDIMMの両方が通常の揮発メモリとして利用できるため、揮発メモリとして使えるメモリの容量がMemory Modeより多い。
* NVDIMMはNUMAの別ノードとして見えるので、numactlなどを使って、RAMとNVDIMMのどちらを使うか切り替えられる。更新頻度が高いデータはRAMの側に、更新頻度が低いけど大きなデータはNVDIMM側に置くといった使い方ができる。とくに後者の特徴は、NVDIMMの耐久性を気にする人にとってはありがたい機能といえるでしょう。
この一連のメールの最後のパッチによると、使い方はいったんdevice DAXの領域を作って、以下のように指定すると利用できるようになるようです。
To make this work, management software must remove the device from being controlled by the "Device DAX" infrastructure: echo -n dax0.0 > /sys/bus/dax/drivers/device_dax/remove_id echo -n dax0.0 > /sys/bus/dax/drivers/device_dax/unbind and then bind it to this new driver: echo -n dax0.0 > /sys/bus/dax/drivers/kmem/new_id echo -n dax0.0 > /sys/bus/dax/drivers/kmem/bind2. PMDKの揮発メモリライブラリがPMDKから外れた
ところで、上記のPMDKの揮発メモリライブラリのところで、「(元)」という記載に気が付いた方はどれくらいいるでしょうか? PMDKのlibvmemとlibvmemallocは、結局PMDKから外れて別のレポジトリになったようです。以前からIntelはこのlibvmemの利用用途では「memkindを使ってほしい」という言い方をしていましたが、とうとうPMDK本体から外されてしまったようです。
https://pmem.io/2019/10/31/vmem-split.html
Windowsではmemkindは対応していないようなので、まだlibvmemの存在には意味があります。が、Linuxでは少なくともlibvmemよりはmemkindを使った方が(あるいは上記のkernelの機能を使う方が)よさそうです。
3. dm-writecache
Device mapperのレイヤでデータをNVDIMMを使ってキャッシュする機能がkernel 4.18でマージされたようです。kernelにはすでにv3.10ぐらいの頃からdm-cacheというSSDなどを使ってキャッシュする機能がありましたが、dm-writecacheでは、NVMeのような高速SSDのほかに、NVDIMMにキャッシュできるのが特徴といえるでしょう。NVDIMMのユースケースとしては遅い記憶媒体をキャッシュするというわりと古典的な使い方かもしれません。しかし、bttのようなブロック単位のデータ保証ではなく、CPUキャッシュ単位での制御を行っているなどしているようで、NVDIMMにより適した制御を行っているようなので、dm-cacheよりは高速性が期待できます。
以下にHuaisheng Yeさんの発表資料がありますから、それを参照するのが概要を知るにはちょうど良いでしょう。図も多くてどんなものかはある程度分かるはずです。
Dm-writecache: a New Type Low Latency Writecahe Based on NVDIMMこれまでのdm-cacheでは、標準ではwrite throughになっていてwrite backはオプションのようですが、それに対してdm-writecacheではwrite backのようです(write throughはないという記事も見かけました)。資料中の図によると、readはキャッシュせず基本的にHDDに直接読みに行くようです。
また、writeでcacheしたデータだけはreadのときにhitするとそちらを読むというようにも見えますが、それが正しいとするとreadは既存のkernelのpage cacheの仕組みに任せてしまって、書き込みのデータ保証をできるだけ早くするという、非常に割り切った作りだといえるでしょう。資料によると、以下のように設定するようです
# lvcreate -n hdd_wc -L 4G vg /dev/sda1 (キャッシュされるデバイス) # lvcreate -n cache_wc -L 1G vg /dev/pmem0 (Filesystem DAXとして設定したNVDIMMのnamespaceのデバイス名) # lvchange -a n vg/cache_wc # lvconvert --type writecache --cachevol cache_wc vg/hdd_wcまた、block deviceとして使うsector modeのnamespaceではなく、Filesystem DAXのnamespaceを使うことができるため、NVDIMMにより最適なアクセスができるようにしているようです。資料にはNVDIMMをsectorモードにしてdm-cacheで使った時に対してFilesystem-DAXモードにして、dm-writecacheで使った時の性能比較や、CephのOSDとしてこれを適用してみた例も載っています。
時間があれば、ちょっとこれも触ってみて検証してみたいところですね。
まとめ
Comsys 2019で話しきれなかったことについて少しですが補足しました。本当はエミュレーション環境などでちょっと試してみたかったのですが、今回は時間切れです。また時間ができた時にそれぞれ試してみたいと思います。
まだまだ媒体が手に入りやすいとは言いづらいですが、LinuxはNVDIMMのために毎年少しずつ進化し続けています。
2020年の動向にも期待しましょう。
- 投稿日:2019-12-24T17:40:48+09:00
シンボリックリンクと遊んだメモ
概要
シンボリックリンクに遊ばれたのでメモ。
シンボリックリンクが中に入っちゃう
以下実行した時、
$ ln -s /home/dir dir
dir
ディレクトリが既に存在している時に実行すると、dir
の配下に/home/dir
のシンボリックリンクが生成されてしまう。シンボリックリンクの削除
rm -rf dir
では削除出来ない。
シンボリックリンクはディレクトリではないからです。$ rm dirでもこちらを使いたい(
rm
はおっかないので)。$ unlink dirシンボリックリンク元ディレクトリよりも上にある.htaccess
無視されるそうです。
先に知れたため、未遂に終わりました。
おわり
デプロイ、CI環境の構築を通してシンボリックリンクに遊ばれました。
ひやひやした場面もあったので、なんとなくでコマンド打たないよう気をつけようと思います。
- 投稿日:2019-12-24T15:18:11+09:00
【unzip】zipファイル内のディレクトリ区切り文字がバックスラッシュなものから任意のファイルのみを解凍する方法
もしかすると当たり前のことかもしれませんが、フィーリングで調べても答えがなかなか見つからなかったので備忘目的でメモします。
事の起こり
unzipコマンドで特定のファイルのみを解凍したい時
下記のようなzipファイルがある場合
hoge.zip$ zipinfo -1 hoge.zip aaa/bbb/ccc.txt ddd/eee/fff.jpg ggg/hhh/iii.logunzip {解凍対象zip} {取り出したいファイルパス} で実現できる。
$ unzip hoge.zip aaa/bbb/ccc.txtがしかし、zipファイル内のディレクトリの区切りがバックスラッシュであった場合
hoge_bs.zip$ zipinfo -1 hoge_bs.zip aaa\bbb\ccc.txt ddd\eee\fff.jpg ggg\hhh\iii.log同じ調子でコマンドを入れるとエラーになってしまう
$ unzip hoge_bs.zip aaa\bbb\ccc.txt Archive: hoge_bs.zip caution: filename not matched: aaabbbccc.txt試行錯誤
シングルクォートで囲めばいいのか?と思うもこれもダメ
$ unzip hoge_bs.zip 'aaa\bbb\ccc.txt' Archive: hoge_bs.zip caution: filename not matched: aaa\bbb\ccc.txt実は展開時にパスがスラッシュに変換されているのか?と試してみるもこれもダメ
$ unzip hoge_bs.zip 'aaa/bbb/ccc.txt' Archive: hoge_bs.zip caution: filename not matched: aaa/bbb/ccc.txt上手くいった方法
バックスラッシュを2つにしてあげると上手くいきました
$ unzip hoge_bs.zip 'aaa\\bbb\\ccc.txt' Archive: hoge_bs.zip warning: hoge_bs.zip appears to use backslashes as path separators inflating: aaa/bbb/ccc.txtこうやってまとめてみると、これって常識なんじゃないかと反省し始めています……
- 投稿日:2019-12-24T14:27:24+09:00
Rubyでじゃんけん
Rubyにてじゃんけん (Rock-Paper-Scissors)
1.じゃんけんの構造化
そもそもじゃんけんとは??
グー、チョキ、パーのどれかを互いに選択し、勝敗を決めるものである。
今回はプレイヤーとプログラムで戦う。
グー Rock
チョキ Paper
パー Scissors
で対戦。
(プログラムでは、乱数を返す、randが活躍するが、それは後々)2.playerの手選択
入力による代入
p 'rock-paper-scissors' player=gets↑rock、paper、scissorsのどれかを選んでもらう
プレイヤーがスペルミスしたときの対応も重要になる3.programの手選択
乱数で決定する。
program_hand=rand(3)↑program_handは0、1、2を乱数で返すことになる。
4.整理
人間にとっては、Rock-Paper-Scissorsという文字表記が分かりやすいが、乱数を0、1、2でやったように、プログラム(機械)からすると、文字よりも数字が分かりやすい。
よって、じゃんけんをしている2人(player、program)において、手を数字と文字であらわしておく
0 グー Rock
1 チョキ Paper
2 パー Scissorsif program_hand==0 program="rock" elsif program_hand==1 program="paper" elsif program_hand==2 program="scissors" endif player=="rock" player_hand=0 elsif player=="paper" player_hand=1 elsif player=="scissors" player_hand=2 end5.じゃんけんの勝敗をつける
ここで、条件分岐が登場
if文を用いるif player_hand==program_hand p 'draw' elsif ((player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 or program_hand==0))and a==1 p 'you win' elsif p 'you lose' end条件の式を書く上で、論理が連続で出てくることがある。(andやor)
論理の優位性などという話があるらしいが、難しい。
先に計算したい方をかっこでくくっちゃうのが早い、簡単、わかりやすい。6.スペルミスに対する対応
rock、paper、scissorsのどれかを選んでもらうことになってるが、
それ以外の入力には、いかにして対応するのか??one more please
と表示させることにaという変数を登場させる。初期値0
rock、paper、scissorsのどれかが記入されたときのみa=1を代入。aをつかって判断する
↓完成
p 'rock-paper-scissors' player=gets a=0 if player=="rock" player_hand=0 a=1 elsif player=="paper" player_hand=1 a=1 elsif player=="scissors" player_hand=2 a=1 end program_hand=rand(3) if program_hand==0 program="rock" elsif program_hand==1 program="paper" elsif program_hand==2 program="scissors" end if a==0 p 'one more please' elsif player_hand==program_hand p 'draw' elsif ((player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 or program_hand==0))and a==1 p 'you win' elsif p 'you lose' end p "player",player,"program",program
- 投稿日:2019-12-24T14:05:27+09:00
ドキュメントから学ぶiptables
以前に何度かiptablesを理解しようと試みて挫折しました。言葉がいつもわかりません。
- テーブルって何ですか?
- チェインって何ですか?
- ターゲットって何ですか?
インターネットにはたくさんの説明がありますが、どうも腑に落ちませんでした。
なので、今回は本腰入れてiptablesのドキュメントを読んで理解したいと思います。2019.12.25 追記
iptablesを理解するために情報収集をする中で、素晴らしいリファレンスの日本語訳サイトを見つけました。
正確な知識をつけるために以下を参照することをおすすめします。
Iptablesチュートリアル 日本語訳
テーブルとチェインの概要をつかむのであれば、Chapter 6を見るのがよいと思います。アウトライン
- まずiptableのマニュアルのDESCRIPTION, TAGETS, TABLESを読み、基礎知識を学び整理します。
- その後、@suinさんの「俺史上最強のiptablesをさらす」を読み解きます。
$ man iptables
以下、iptables 1.6.1のマニュアルを抜粋して読みます。
DESCRIPTION
DESCRIPTIONを読むことで以下が分かります。
- iptablesが何をするものか
- テーブルとチェインとターゲットの概念
Iptables and ip6tables are used to set up, maintain, and inspect the tables of IPv4 and IPv6 packet filter rules in the Linux kernel. Several different tables may be defined. Each table contains a number of built-in chains and may also contain user-defined chains.
- Linux kernelには、IPv4, IPv6 のパケットフィルターのルールのテーブルがある。
- iptablesはそのset upとmaintainとinspectをするために使用される。
- いくつかの異なるテーブルが定義されているだろう。
- 各テーブルはビルトインのチェインやユーザ定義のチェインがあればそれを含む。
Each chain is a list of rules which can match a set of packets. Each rule specifies what to do with a packet that matches. This is called a `target', which may be a jump to a user-defined chain in the same table.
- 各チェインはパケットの集合にマッチするルールのリストである。
- 各ルールはマッチしたパケットに何をするか指定する。
- この「何をするか」をターゲットと呼ぶ。
(同じテーブル内のユーザ定義のチェインへのジャンプということもある。まとめ
- テーブルはLinux kernelに存在するパケットフィルターのテーブルを指す
テーブルは複数存在し、各テーブルがチェインを含む- チェインはパケットをマッチさせるルールのリスト
- ターゲットはマッチしたパケットに何をするか。
TARGETS
TARGETSを読むことで以下が分かります。
- パケットマッチしなかったらどう処理されるか、マッチしたらどう処理されるか
- ACCEPT, DROP, RETURNというターゲットについて
A firewall rule specifies criteria for a packet and a target. If the packet does not match, the next rule in the chain is examined; if it does match, then the next rule is specified by the value of the target, which can be the name of a user-defined chain, one of the targets described in iptables-extensions(8), or one of the special values ACCEPT, DROP or RETURN.
- パケットがマッチしなかったら、次のルールを試す。
- パケットがマッチしたら、次のルールはターゲットの値によって指定される。
- そのターゲットの値はユーザ定義のチェインか、特別な値である、ACCEPT, DROP, RETURNである。
ACCEPT means to let the packet through. DROP means to drop the packet on the floor. RETURN means stop traversing this chain and resume at the next rule in the previous (calling) chain. If the end of a built-in chain is reached or a rule in a built-in chain with target RETURN is matched, the target specified by the chain policy determines the fate of the packet.
- ACCEPTはパケットを通過させる
- DROPはパケットを床に落とす、つまり棄てる
- RETURNはチェインの走査を停止し、呼び出し元のチェーンの次のルールから再開する
- ビルトインのチェインの終わりに達するか、ビルトインチェインでRETURNにマッチした場合、そのパケットの運命は、そのチェインのポリシーによって指定される
まとめ
- ターゲットにはACCEPT, DROP, RETURNがある
- ビルトインチェインのルールに一つもマッチしなかった、パケットの扱いはポリシーによって指定される
※REJECTはman iptables-extensionsを参照ください。
TABLES
TABLESを読むことで以下が分かります。
- 複数存在するテーブルには何があるか
- filterテーブルに存在するビルトインのチェインとは
There are currently five independent tables (which tables are present at any time depends on the kernel configuration options and which modules are present).
The tables are as follows
- カーネルのコンフィグによるが、現在5つの独立したテーブルがある
- それはfilter, nat, mangle, raw, securityである
ここではfilterテーブルとnatテーブルのみ意訳します。
filter:
This is the default table (if no -t option is passed). It contains the built-in chains INPUT (for packets destined to local sockets), FORWARD (for packets being routed through the box), and OUTPUT (for locally-generated packets).
- filterテーブルは-tオプションを使わなかった場合のデフォルトのテーブルである
- このテーブルはビルトインの以下のチェインをもつ
- INPUT(ローカルソケット宛のパケット用)
- FORWARD(通過するようにルーティングされるパケット用)
- OUTPUT(ローカルで生成されるパケット用)
nat:
This table is consulted when a packet that creates a new connection is encountered. It consists of four built-ins: PREROUTING (for altering packets as soon as they come in), INPUT (for altering packets destined for local sockets), OUTPUT (for altering locally-generated packets before routing), and POSTROUTING (for altering packets as they are about to go out). IPv6 NAT support is available since kernel 3.7.
- natテーブルは新規コネクションを作成するパケットを検出すると参照される
- このテーブルはビルトインの以下の4つのチェインをもつ
- PREROUTING(入って来るパケットの改変用)
- INPUT(ローカルソケット宛のパケットの改変用)
- OUTPUT(ローカルで生成されるパケットのルーティング前の改変用)
- POSTROUTING(出て行くパケットの改変用)
filterテーブルの図解
ここまでの理解を図にしてみます。
- filterテーブルがあり、その中にはビルトインの3つのチェインとユーザ定義のチェインがある
- ローカルソケット宛のパケットはINPUTチェインで、通過するようルーティングされるパケットはFORWARDチェインで、ローカルで生成されるパケットはOUTPUTチェインで、ルールにマッチするか調べられる
- 各チェインにはルールがありマッチすれば、ACCEPT, DROP, RETURNされるかユーザ定義のチェインにジャンプする
- マッチしない場合はチェインのポリシーでACCEPTかDROPに処理が決まる
「俺史上最強のiptablesをさらす」を読み解く
ここまでの得た知識で抜粋して読んでみます。
ポリシーの決定
iptables -P INPUT DROP # すべてDROP。すべての穴をふさいでから必要なポートを空けていくのが良い。 iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP-P, --policy chain target
ビルトインのチェインにポリシーを設定しています。
パケットがチェインのルールリストにマッチしなかった場合の最終的な扱いを決めています。INPUTとFORWARDチェイン(に含まれるルールリスト)の対象となるパケットが、いずれのルールにもマッチしなかった場合にはDROPするとしています。
まず、このポリシーを設定し、これ以降でACCEPTターゲットを持つルールを追加することで許可するパケットを決めていきます。
これがホワイトリスト言われる理由だと思います。信頼可能なホストは許可
# lo はローカルループバックのことで自分自身のホストを指す iptables -A INPUT -i lo -j ACCEPT # SELF -> SELF-A, --append chain rule-specification
-i, --in-interface name
-j, --jump targetINPUTチェインにルールを追加しています。
loに来たパケットにマッチし、ACCEPTしています。攻撃対策: Stealth Scan
iptables -N STEALTH_SCAN # "STEALTH_SCAN" という名前でチェーンを作る iptables -A STEALTH_SCAN -j LOG --log-prefix "stealth_scan_attack: " iptables -A STEALTH_SCAN -j DROP-N, --new-chain chain
STEALTH_SCANというユーザ定義のチェインを作成し、そのチェインに、LOG, DROPへjumpするルールをappendしています。
LOGターゲットは処理を行った後に終了せず、次のルールを処理します。
※man iptables-extentionsのLOGを参照This is a "non-terminating target", i.e. rule traversal continues at the next rule.
上記設定は、STELALTH_SCANチェインにログを残すルールとパケットを破棄するルールを登録しています。
このチェインにjumpしたら順にログを残し、パケットが破棄されます。そしてこのチェインへのジャンプのするための設定が以下です。
# ステルススキャンらしきパケットは "STEALTH_SCAN" チェーンへジャンプする iptables -A INPUT -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j STEALTH_SCAN ...# 続きは省略ステルススキャンと思しきパケットをマッチしSTELALTH_SCANチェインにジャンプするルールをINPUTに追加しています。
※ -p tcp --tcp-flags, -m state --state NEWはステルススキャンと思しきパケットをマッチさせる設定だと思います。
ここではステルススキャンについては扱わないので深入りしません。-j ACCEPT等がjumpであるのに最初は違和感がありましたが、
ACCEPT, DROP, RETURNを特殊なチェインと捉えることで違和感はなくなりました。まとめ
ここまでで大筋が読めるようになりました。
パケットをマッチさせるオプションを学習すればとりあえず、「俺史上最強のiptablesをさらす」は理解できそうです。ただし、「俺史上最強のiptablesをさらす」はINPUTチェインへのルール登録のみのため、
FORWARDやOUTPUTに関して理解ができていません。
次回はこの辺をもう少し掘ってみたいと思います。2019.12.25 追記
iptablesを理解するために情報収集をする中で、素晴らしいリファレンスの日本語訳サイトを見つけました。
正確な知識をつけるために以下を参照することをおすすめします。
Iptablesチュートリアル 日本語訳
テーブルとチェインの概要をつかむのであれば、Chapter 6を見るのがよいと思います。この記事がもしあなたの理解の助けになったら幸いです。
「いいね」よろしくお願いします。
- 投稿日:2019-12-24T12:30:49+09:00
Ruby数字出力 0埋め
Rubyでの計算結果を0埋めで表示(3→003)
今回、例として、入力された数値の倍の数を三桁の0埋めで考える。
1→002
2→004
6→0121.まず普通の表示
1→2
2→4
6→12
をまず、できるようにするputs "数字を入れてください" number = gets.to_i number2=number*2 print number2↑これで、完了。
2.0埋めを考えていく
sprintf("%03d",number)や
"%03d" % numberが、numberの0埋め3桁表示となる。
実行するプログラム
puts "数字を入れてください" number = gets.to_i number2=number*2 number3=sprintf("%03d", number2) print number31→002
2→004
6→012が確認できた
参考文献
- 投稿日:2019-12-24T12:07:50+09:00
memo
Linuxコマンド
・操作しているディレクトリを表示するコマンド
$ pwd [vagrant@localhost ~]$ pwd /home/vagrant
- 投稿日:2019-12-24T07:15:22+09:00
sudo がアレなので doas に乗り替えてみる
最初にまとめ
長くなってしまったのでまとめを最初に。
- sudoを使うのをやめdoasに乗り替えた
- doasには必要十分な機能があり、sudoに比べ設定がシンプル
- doasは元々がOpenBSDプロジェクトのものであり、セキュリティ面でも信頼できる
- FreeBSDではpkgでインストール
- Linuxではソースからインストール (記事ではDebian/Ubuntu, CentOSでの例を紹介)
- doasのソース規模はsudoのそれに比べかなり小さい
最近manやmakeを知らない人が居るという事実を見かけるので、その辺りの利用例も含めて少々回りくどく記述しています。
本記事はSoftware Design誌 2020年1月号 に掲載された「sudoからdoasへ」の内容とほぼ同じもの、というより元の原稿そのものです。ネタを明かすと、元々Qiitaで公開するつもりで書いたのですが、書きすぎてボリュームが雑誌の記事になるぐらいになってしまったのをSoftware Designに拾っていただいたというのが本当の話です。
sudoコマンド
sudoコマンドはUNIX系オペレーティングシステムでは、システム管理に必須と言えるほど広く普及しているコマンドです。
元々UNIXにはsuコマンドが標準で用意されていて、各種BSDやLinuxの各ディストリビューションにもあります。suはユーザーが一時的にroot権限を得るために必要なコマンドで、実行するとroot環境のシェルが起動しますが、特権を得るためにはrootのパスワードが必要です。rootではない任意のユーザー権限を得ることもできますが、その権限を得ようとするユーザーのパスワードが必要になります。つまりsuではこれから利用する権限のユーザーのパスワードが必要になるわけです。
サーバーが1台で管理者がそのままユーザーであるような場合、特権を利用するときはsuでも問題はありません。ここでサーバーが複数あってそれぞれのrootのパスワードが共通の場合に、特定のユーザーに特定のサーバーだけの特権を与える必要がある場合を考えてみましょう。suを使うためには、rootのパスワードが全てのサーバーで共通のままではそのユーザーが別のサーバーでも特権を得られてしまいますから、当該サーバーのみrootのパスワードを別にする等の対応が必要になります。このようなことを複数のサーバーで設定すると、結局rootのパスワードをそれぞれ変えるとか、root以外の特権ユーザーを用意するなどの対応が必要となってしまいます。
sudoでは、特権を得るために必要なパスワードにユーザー自身のパスワードを使うことで前述の問題を解決しています。もちろん誰もが自分のパスワードで特権が得られては問題ですから、設定ファイルでユーザーを限定し、さらに特権を使って利用できるコマンドの限定等もできるようになっています。また設定ファイルを編集するためのvisudoという専用のコマンドも付属しています。
筆者が個人的にsudoが便利だと思っているのは次の3点です。
- ユーザー自身のパスワードで特権を得られ、特権時に使えるコマンドを限定できる。必要に応じてパスワード無しで特権を得る設定にもできる。
- sudoに続いてコマンド名を指定することで、そのコマンドのみ特権で実行できるので1コマンドのみ特権が必要な場合等に便利。
- 一度sudoでパスワードを入力すると、ある一定時間内にsudoを使う場合はパスワードの入力を省略でき、連続して特権で作業する場合の利便性が高い。
2番めの項目について少し補足すると、suでも引数を指定することで特権を使った1コマンドだけの実行はできますが、パラメータの指定が煩雑で使い勝手はsudoに軍配が上がります。
sudoの脆弱性問題
sudoのように特権を扱うコマンドではどうしても脆弱性問題がつきまといがちです。つい最近にも(2019年10月中旬) sudo の脆弱性が話題になりました。この脆弱性は、sudoが利用できる設定になっているユーザーであれば設定の制限を超えた特権を得られてしまうというものでした。
sudoはすでに30年ぐらい歴史のある古いコマンドで、筆者が利用するようになったのは90年代前半頃からですが、以来時折sudoの脆弱性の報告を見ています。sudoは管理機能が豊富で便利な設定もできますが、あれば確かに便利でも実際はほとんど使われないような機能が多数用意されています。設定マニュアルを読んで全機能を理解するのはとても大変で、個人的には「こんなに機能要らないんだが…」と思いつつ使っているのが現状です1。
今回のsudoの脆弱性の報告を見て「またsudoに脆弱性か」と思ったわけですが、そこで最近のOpenBSDには標準コマンドとしてdoasが用意されていることを思い出しました。筆者の使い方であればsudoまでの機能は全く不要なので、この機会にとdoasを導入してみました。
doasコマンド
doasもユーザー自身のパスワードを使って特権を得る点は、sudoと同様です。違いはと言えばsudoに比べて非常に機能が限定されていて、このぐらいの機能は必要だと思われる最小限の機能しかないことです。例えばsudoにはコマンドやユーザーのエイリアス等を活用して、複数のユーザーやコマンドをグルーピングして管理できますが、doasにはそういった管理に便利な機能は全くありません。しかしざっくり見たところ、筆者の利用範囲ではdoasでも困ることは無さそうですし、多くの利用範囲はこれだけあれば十分だろうと思います。
前述の通りdoasはOpenBSDのプロジェクトから生まれたコマンドの一つです。OpenBSDはセキュリティ面に非常に注力したオペレーティングシステムで、今や当たり前のように広く使われているSSHの代表的な実装のOpenSSHもOpenBSDプロジェクトからの産物です。そのような点でOpenBSDから生まれたdoasは、セキュリティ面でも信頼できると言えます。2。
doasのインストール
FreeBSD編
筆者は普段メインの環境としてFreeBSDを利用しているので、doasならすでにportsにあるだろうと思い調べてみると、予想通りsecurity/doasにありpkgも存在していました。そこでdoasのpkgをインストールします。
$ sudo pkg install doas Updating FreeBSD repository catalogue... ----- (中略) ----- New packages to be INSTALLED: doas: 6.2 Number of packages to be installed: 1 Proceed with this action? [y/N]: y [1/1] Installing doas-6.2... [1/1] Extracting doas-6.2: 100% ===== Message from doas-6.2: -- To use doas, /usr/local/etc/doas.conf must be created. Refer to doas.conf(5) for further details. ----- (以下略) -----doas.confの設定
インストールが終わったら、次はdoasの設定です。インストール時のメッセージからわかるように、設定は /usr/local/etc/doas.conf に記述します。パッケージではdoas.confの編集の元になるファイル等は用意されないためゼロから設定する必要がありますが、難しくはありません。実は筆者がdoasを設定するのはこれが初めてでは無く、過去にOpenBSDで設定して利用した経験があります。しかしその時は、doasの最低限の設定を1行書いただけでした。
何はともあれ新規のコマンドを設定する場合に重要なのは、マニュアルの確認です。manコマンドを使ってdoas.confの設定方法を確認します。
$ man doas.conf DOAS.CONF(5) FreeBSD File Formats Manual DOAS.CONF(5) NAME doas.conf - doas configuration file SYNOPSIS /usr/local/etc/doas.conf DESCRIPTION The doas(1) utility executes commands as other users according to the rules in the doas.conf configuration file. The rules have the following format: permit|deny [options] identity [as target] [cmd command [args ...]] Rules consist of the following parts: permit|deny The action to be taken if this rule matches. ----- (以下略) -----この通りdoas.confでは、1行に1個のルールを記載し、後半に記載がありますが '\' はエスケープとして働くという、比較的よく見かける馴染みのある仕様です。manの後半には設定例がありますがそれを含めても拍子抜けするほど全体が短く、sudoの設定ファイルであるsudoersに比較して設定項目がとても少ないことがわかります。例えばoptionsのキーワードは nopass, keepenv, sentenv, persist の4種類しかありません。
通常各行は permit に続き必要なオプションを記入し、identityにはユーザー名やグループ名を設定します。またグループ名を指定する場合は :wheel のようにグループ名の先頭に : をつけます。前述の通りマニュアルには設定例も記載されているので、参考にすると良いでしょう。
doasではsudoにあるvisudoのような設定ファイル編集のための専用コマンドは用意されていないので、vi, vim, nano等使い慣れたエディタで/usr/local/etc/doas.confを用意します。以下の例では1行のdoas.confを用意しています。
$ sudo vi /usr/local/etc/doas.conf ----- (doas.confの編集) ----- $ ls -l /usr/local/etc/doas.conf -rw-r--r-- 1 root wheel 64 Oct 21 18:11 /usr/local/etc/doas.conf $ cat /usr/local/etc/doas.conf permit nopass minmin $この例のようにdoas.confのパーミッションは、rootがオーナーで644あるいは必要に応じて640や600に設定します。筆者は普段ユーザー名にminminを使っているのでここではそのまま例として示しています。
設定ではoptionsにnopassオプションを加えることでパスワード無しで任意のコマンドを特権で実行できるようにしています。 nopassオプションによるパスワード無しの設定はリスクもありますので筆者がこれを推薦しているわけではありません。あくまでもこれは筆者の個人サーバーの設定であり、他のサーバーではnopassオプションの有無を使い分けています。
最初のところで書いたように、sudoには一度sudoを実行すると繰り返し利用する場合にしばらくの間(デフォルトでは15分間)パスワード入力を省く機能があり、doasにも同様の機能を提供するpersistオプションが用意されているようですが、OpenBSDでしか利用できないと記載されてます。
persist After the user successfully authenticates, do not ask for a password again for some time. Works on OpenBSD only, persist is not available on Linux or FreeBSD.sudoでは visudoが編集後にsudoresのシンタックスチェックを行い、誤りがあると警告して終了せずに再度編集できますが、doas.confでは普通のエディタを利用する事もありそのような編集時に設定ミスを確認することはできません。その代わりにdoas自身に-Cオプションで設定ファイルをチェックする機能があります。
$ doas -C /usr/local/etc/doas.conf $エラーがあればエラーメッセージを表示しますので、ここでdoas.confが問題無く記述できていることがわかります。
万が一doas.confに設定ミスがあるとdoasが実行できなくなるリスクもあるので、既存のdoas.confを編集するのであれば、次のような手順を行うほうが安全と言えます。
$ cp /usr/local/etc/doas.conf /tmp ← 一旦 /tmp に doas.conf をコピーする $ vi /tmp/doas.conf ← doas.conf の編集 <必要な編集を行う> $ doas -C /tmp/doas.conf ← 編集後の設定確認 $ doas install -m 644 /tmp/doas.conf /usr/local/etc ← doas.confを更新 $doasのオプションは
man doas
で調べられますが、簡単にリストアップしておきます。
- -a: /etc/login.confに記載したdoasの認証方式を指定する
- -C ファイル名: 指定ファイルをdoasの設定ファイルとしてシンタックスをチェックする
- -n:ノンインタラクティブな利用時に指定する(ssh 経由リモートサーバーでdoasを利用するときなど)。
- -s:ユーザーのシェルを起動する
- -u ユーザー名:指定したユーザーの権限を得る(デフォルトはroot)
- --:以降に指定したものを実行コマンドと引数として解釈する
doasが動作するかどうかを確認するには、idコマンドが最適です。
$ doas id uid=0(root) gid=0(wheel) groups=0(wheel),5(operator) $この通りuidが0、つまりroot権限でidコマンドを実行でき特権が得られていることがわかります。
doas利用時の環境変数
個人的に一番気になるのは、doasを利用した場合の環境変数PATHの扱いです。というのも例えばホームディレクトリの~/bin には個人的に利用するコマンドがあってシステム管理作業でも同じものを利用したい場合、PATHが変わってしまうとそれらのコマンドが単純には利用できなくなります。doasで特権に切り替わった時でも、普段設定してあるPATHを引き継ぎげれば都合が良いわけです。
まずはPATHがどうなるかをenvコマンドで確認してみます。
$ doas env | fgrep PATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin $doasの環境下のデフォルトではこのようなPATHになり自分のPATHは引き継がれていないことがわかります。個人のPATHを引き継ぐ目的には、ぱっと見keepenvオプションを使えばよさそうだったので、設定してためしてみました。
$ cat /usr/local/etc/doas.conf permit nopass keepenv minmin $ doas env | fgrep PATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin $PATHに変化はありません。そこで改めて doas.confの keepenvのところをよく眺めてみます。
keepenv The user's environment is maintained. The default is to reset the environment, except for the variables DISPLAY and TERM. ----- (中略) ----- Note: The target user's PATH variable can be set at compile time by adjusting the GLOBAL_PATH variable in doas's Makefile. By default, the target user's path will be set to "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"最後の Note: の節からわかるように、doasでの特権ユーザー時のPATHは、コンパイル時に指定したものが設定されるようになっています。
一般論として、環境変数PATHをそのまま特権に引き継ぐのはセキュリティ面でリスクがあります。ですからdoasを実行したときのデフォルトのPATHはこのようになっているわけですが、管理者が必要に応じて自分のPATHを引き継ぐのは問題ありません。確認したところ、ユーザーのPATHを引き継ぐためには明示的にsetenvオプションで指定すればよいことがわかりました。
$ cat /usr/local/etc/doas.conf permit nopass keepenv setenv { PATH } minmin $ doas env | fgrep PATH PATH=/home/minmin/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/sbin:/usr/sbin $結局筆者の個人サーバーのdoas.confは、次のように2行設定しました。
$ cat /usr/local/etc/doas.conf permit nopass setenv { PATH } minmin permit nopass keepenv root $rootユーザーであっても doasを利用する場合に何も設定が無いとdoasの実行を拒否されるため、2行目はそれを回避するための設定です。
なおmanの記述ではユーザーの HOME変数はkeepenvで引き継げるように読めるのですが、試したところどうもそうではなさそうで、HOME変数を引き継ぐのであれば setenvで明示的に指定する必要があります。筆者の場合はFreeBSDでデスクトップ環境を使うことは無いので問題になりませんが、デスクトップ環境を使う場合は、環境変数を適正に引き継がないとアプリケーションのクラッシュを招くことがありそうです。
doasの実行記録
doasを実行すると、FreeBSDの場合は /var/log/auth.log に記録されます(sudoも同じ)。auth.log を見るのにも特権が必要となりますので、doasを使って確認します。
$ doas id uid=0(root) gid=0(wheel) groups=0(wheel),5(operator) $ doas tail -2 /var/log/auth.log Oct 28 23:11:17 fbsdsvr doas: minmin ran command id as root from /home/minmin Oct 28 23:11:37 fbsdsvr doas: minmin ran command tail -2 /var/log/auth.log as root from /home/minmin $なお tail に -2 と2行分だけに限定しているのは、本サンプルを作るために余計なものを表示させないようにしているだけであり、それ以上の意味はありません。
Linux編
前項で引用したマニュアルのpersistのオプションのところにLinuxの文字列があることで、Linuxでもdoasが使えることに気づき、実際にLinuxにも導入してみました。一口にLinuxと言っても様々なディストリビューションがありますが、ここではDebian/Ubuntu、CentOSで試しています。
doasのソースの入手
doasはsudoのように現時点で誰にでも知られているコマンドでは無いので、調べた範囲ではLinux用のパッケージはまったくありません。doasをインストールするためには、ソースを用意してコンパイルとインストールの作業を行う必要があります。
doas自体はOpenBSDの一部であるため、ソースはOpenBSDのソースツリーに含まれています。しかしOpenBSDのソースからdoasの部分だけ持ってきてもそのままでは不足するものがあり、他のOSでのコンパイルはすんなりとはいきません。そこでLinux等の他OSでもコンパイルできるようにソースレベルでパッケージ化したものがGithubで公開されています。
https://github.com/slicer69/doas/
まずはこのソースをgit cloneでローカルに展開します。
$ git clone https://github.com/slicer69/doas.git ----- (中略) ----- $ cd doas $ ls -F LICENSE README.md doas.1 doas.conf.5 env.c Makefile compat/ doas.c doas.h parse.y $ファイルの一覧からわかるようにdoasはCで記述されています。
doasのコンパイルとインストール (Debian/Ubuntu編)
README.mdによると、Linuxでは
make
でコンパイル、make install
でインストールできると書いてあり、難しいところは何も無さそうです。そこで早速コンパイルしてみましょう。以下はDebianで試していますが、Ubuntuでもまったく同様にできることを確認しています。DebianでCのプログラムをコンパイルするためには、開発環境であるbuild-essentialのパッケージが必要なので、まずbuild-essentialをインストールします。
$ sudo apt update ----- (中略) ----- $ sudo apt -y install build-essential ----- (中略) ----- $もちろんすでに開発環境を準備済であればこの作業は省略できます。
では実際にコンパイルしてみましょう。
$ make cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o doas.o doas.c doas.c:48:31: fatal error: security/pam_appl.h: そのようなファイルやディレクトリはありません #include <security/pam_appl.h> ^ compilation terminated. <ビルトイン>: ターゲット 'doas.o' のレシピで失敗しました make: *** [doas.o] エラー 1 $ヘッダーファイルであるpam_appl.hが見つからないとエラーになりコンパイルが停止します。調べてみるとpam_appl.hは、PAMを利用するプログラムを作る際に必要なものです。実際README.mdには、libpam0g-devが必要であるという記載がありました(README.mdはよく読まないといけないですね ^^;)。
$ sudo apt -y install libpam0g-dev気を取り直してあらためてコンパイルしてみます。
$ make cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o doas.o doas.c cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o env.o env.c cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o compat/execvpe.o compat/execvpe.c compat/execvpe.c: In function ‘execvpe’: compat/execvpe.c:61:5: warning: nonnull argument ‘name’ compared to NULL [-Wnonnull-compare] if ( (! name) || (*name == '\0') ){ ^ cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o compat/reallocarray.o compat/reallocarray.c yacc parse.y make: yacc: コマンドが見つかりませんでした Makefile:54: ターゲット 'y.tab.o' のレシピで失敗しました make: *** [y.tab.o] エラー 127 $yaccコマンドが無いとエラーになってコンパイルが終了し、build-essentialにはyaccが含まれていないことに気づきました(そりゃそうか)。Debianでは数種類のyaccの実装からパッケージを選択できるようになっていますが、ここではBerkeley版のyaccであるbyaccをインストールして改めてコンパイルします。byaccの替わりにbisonをインストールしてもDebian/Ubuntuではそのままコンパイルできます。
$ sudo apt -y install byacc ----- (中略) ----- $ make yacc parse.y cc -include compat/compat.h -Icompat -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -c y.tab.c ----- (中略) ----- cc -o doas doas.o env.o compat/execvpe.o compat/reallocarray.o y.tab.o compat/closefrom.o compat/errc.o compat/getprogname.o compat/setprogname.o compat/strlcat.o compat/strlcpy.o compat/strtonum.o compat/verrc.o -lpam -lpam_misc $コンパイルが無事終了したので、あとはインストールです。
$ sudo make install mkdir -p /usr/local/bin cp doas /usr/local/bin/ chmod 4755 /usr/local/bin/doas mkdir -p /usr/local/man/man1 cp doas.1 /usr/local/man/man1/ mkdir -p /usr/local/man/man5 cp doas.conf.5 /usr/local/man/man5/ $実行されるコマンドからわかるように、
make install
で実際にインストールされるファイルは次の表の通りです。
ファイル ファイルの内容 /usr/local/bin/doas doasのプログラム本体 /usr/local/man/man1/doas.1 doasコマンドそのものmanファイル /usr/local/man/man5/doas.conf.5 doas.confのmanファイル この程度のファイル数であれば、パッケージになっていないプログラムでも管理は難しくありません。もし将来 doasをアンインストールする必要ができた場合は、これらの3つのファイルと、doasの設定ファイルである/usr/local/etc/doas.confを削除するだけです。
doasがインストールできたら次はdoas.confを用意しますが、ここではFreeBSDで設定したものと同じものを/usr/local/etcに用意しました。
Debianの場合もdoasの実行はFreeBSDと同様に/var/log/auth.logに記録されますが、実行するコマンドにオプションを指定する場合は、オプション解析ライブラリの関係で明示的にコマンドとの区切りを示す”--”を指定する必要があるようです。
$ doas id uid=0(root) gid=0(root) groups=0(root) $ doas tail -2 /var/log/auth.log doas: invalid option -- '2' ← ‘2’ が doas 自身のオプションと扱われてエラー usage: doas [-ns] [-a style] [-C config] [-u user] command [args] $ doas -- tail -2 /var/log/auth.log Oct 30 23:50:25 idmdemo0 doas: minmin ran command id as root from /home/minmin Oct 30 23:50:35 idmdemo0 doas: minmin ran command tail -2 /var/log/auth.log as root from /home/minmindoasのコンパイルとインストール (CentOS編)
CentOSでもdoasのコンパイルとインストールを試してみました。
PAMのヘッダーファイルやyaccについては準備しないとDebianと同じ問題が起きますので、Cのコンパイルに必要なgcc, makeと共にインストールしています。PAMの開発環境はpam-develのパッケージ、yaccについてはbyaccのパッケージを利用しています。
$ sudo yum install gcc make pam-devel byacc ----- (中略) ----- $ make cc -Wall -O2 -DUSE_PAM -DDOAS_CONF=\"/usr/local/etc/doas.conf\" -D_GNU_SOURCE -include compat/compat.h -Icompat -c -o doas.o doas.c ----- (中略) ----- cc -o doas doas.o env.o compat/execvpe.o compat/reallocarray.o y.tab.o compat/closefrom.o compat/errc.o compat/getprogname.o compat/setprogname.o compat/strlcat.o compat/strlcpy.o compat/strtonum.o compat/verrc.o -lpam -lpam_misc $ sudo make install ----- (中略) ----- $インストールされるファイルについては、Debianの場合となんら差異はありません。
CentOSの場合、既にbisonがインストール済であればbyaccを新たにインストールする必要は無く、
make YACC=’bison -y’
としてmakeを起動すればコンパイルできます。すでに紹介したOS同様doas.confを設定して、idコマンドでdoasの動作を確認します。またCentOSの場合doasの実行記録は、/var/log/secureになります。
$ doas id uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 $ doas -- tail -2 /var/log/secure Oct 30 23:56:56 centos0 doas: minmin ran command id as root from /home/minmin Oct 30 23:57:15 centos0 doas: minmin ran command tail -2 /var/log/secure as root from /home/minmindoasとsudoでソースの規模を比較してみる
doasのソースをローカルに展開したので、この際と思いsudoとのソースの規模を単純に行数で比較してみました。
sudo のソース規模
sudoはこの記事の作成時点で最新のsudo-1.8.28p1.tar.gzで数えてみます。
$ tar xf sudo-1.8.28p1.tar.gz $ cd sudo-1.8.28p1 $ ls -F ABOUT-NLS config.h.in ltmain.sh ChangeLog config.sub m4/ INSTALL configure* mkdep.pl* INSTALL.configure configure.ac mkinstalldirs* MANIFEST doc/ mkpkg* Makefile.in examples/ pathnames.h.in NEWS include/ plugins/ README indent.pro po/ README.LDAP init.d/ pp* aclocal.m4 install-sh* src/ autogen.sh* lib/ sudo.pp config.guess log2cl.pl* $sudoではsudoの本体の他に多数のライブラリのファイル等が含まれているので、srcディレクトリ下の、Cのソースファイル(*.c)とヘッダーファイル( *.h)を数えてみました。3
$ find src -type f -name '*.[ch]' | xargs wc -l 76 src/preload.c 735 src/exec_monitor.c ----- (中略) ----- 113 src/sudo_exec.h 197 src/get_pty.c 12612 total $12600行ほどあるのがわかります。ちなみにソースパッケージに含まれている *.[ch] に該当するファイルをすべて数えると10万行以上あり、正直とても驚きました。
doas のソース規模
これに対してdoasは次の通り約1200行で、sudoに比較してとても小規模であることがわかります。
$ wc *.[chy] 564 1716 13080 doas.c 65 238 1673 doas.h 230 693 4951 env.c 343 1044 6831 parse.y 1202 3691 26535 total $OpenBSDではないOSへ移植するために必要なライブラリ等が compat ディレクトリ下にありますが、これらをすべて数えてもdoasのソースパッケージは3000行程度です。
ソース規模が小さければ良いというわけでは無いですが、大きければそれだけ不具合を含むリスクが高まるのも事実です。
doasのインストール後
筆者の場合sudoでは-uと-sオプションを使うことが多いのですが、doasでもこれらは同じオプションの指定で同じ機能を利用できます。つまり筆者の利用範囲ではdoasがあればsudoは不用なので、FreeBSD環境ではシステムからはsudoをアンインストールしました。
しかし自作のシェルスクリプト内部でsudoを利用しているものがあるため、書き換えるまでの当面の対応も含めてsudoはdoasへのシンボリックリンクに設定しました。
$ doas ln -s doas /usr/local/bin/sudo $ ls -l /usr/local/bin/sudo lrwxr-xr-x 1 root wheel 4 Oct 22 10:46 /usr/local/bin/sudo -> doas $ ls -lL /usr/local/bin/sudo -rwsr-xr-x 1 root wheel 28800 Oct 4 05:20 /usr/local/bin/sudo $ sudo id ← ここからのsudoの実体はdoas uid=0(root) gid=0(wheel) groups=0(wheel),5(operator) $ sudo -V ← sudoは-Vでバージョンを表示するがdoasには無いオプション doas: illegal option -- V usage: doas [-ns] [-a style] [-C config] [-u user] command [args] $Linuxの場合、一部のLinuxのディストリビューションではsudoがあることを前提にシステムが構築されている場合もありますので、もしsudoをシステムから削除するのであれば、doasはsudoとは完全に同じでは無いことを良く理解した上で問題無いと判断できてからにしましょう。無理にsudoを削除する必要はありません。
最後に
sudoに脆弱性が発見されても、コマンドの性質上リモートから攻撃される危険性はありません。特に自分1人あるいは信頼できる人だけで利用しているサーバーであれば、慌てて対策を取る必要もありません。その意味でプライベートで利用しているサーバーでわざわざdoasに乗り替える必要性はあまり無いのかもしれませんが、個人的には同じ機能を利用するのであればシンプルなものを選ぶことにしていることもあり、今回doasを導入したのはは正解だったと考えています。