20191224のLinuxに関する記事は11件です。

Linux Network NamespaceをGoで操作する

TL;DR

  1. Go言語のgoroutineはdefaultではpreemptiveに動作するOS Threadが切り替わるのでOS Threadに強く紐づくlinuxのnamespace関連の操作を行うときはruntime.LockOSThread()しておく必要がある。1
  2. 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のライブラリ自体は軽量なのでぜひとも実装自体を覗いてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/vagrant

clear, Ctrl + l:画面のリセット

cd:ディレクトリ移動

cd [ディレクトリ名]:[ディレクトリ名]に移動

[vagrant@localhost ~]$ pwd
/home/vagrant
[vagrant@localhost ~]$ cd unix_lessons/
[vagrant@localhost unix_lessons]$ pwd
/home/vagrant/unix_lessons

cd ..:一つ上のディレクトリへ移動

[vagrant@localhost unix_lessons]$ pwd
/home/vagrant/unix_lessons
[vagrant@localhost unix_lessons]$ cd ..
[vagrant@localhost ~]$ pwd
/home/vagrant

cd -:直前のディレクトリに戻る

[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_lessons

cd !$:直前のコマンドに渡した最後の文字列を引用してファイル移動

[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
myapp

cp :ファイルのコピー

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
myapp3

rmdir:ファイルの削除

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
myapp

cat:ファイルの中身確認

[vagrant@localhost unix_lessons]$ cat ./myapp/hello.txt

・ファイルの中身の確認が可能
image.png

less:ファイルの中身確認

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_lessons

help, 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...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ライフ楽しんでください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/1346

NVDIMMにはもともと癖のある不揮発な性質よりも、不揮発メモリの大容量性のみに着目して従来のRAMと同じように不揮発メモリを使いたいという期待もありました。このため、Intelはこれまで以下の方法を提供してきました。

A) Memory Mode

ハード/ファームの設定により、RAMをcacheにしてNVDIMMの領域を通常のMemory領域に見せるというModeです。(これに対して、NVDIMMを本当に不揮発メモリに見せるModeはApp Direct Modeと呼びます)。

Memory Modeではソフトの変更を一切行わずに済むのが長所です。しかし、RAMをキャッシュとして使ってしまうので、RAMの容量はメモリサイズとしては見えなくなってしまい、図のようにNVDIMMの容量がそのままメモリサイズとなります。また、キャッシュであるのでキャッシュミスしたときのレイテンシーの変化などはソフト側で予測できず、またコントロールできないのも欠点です。

Memory_mode.jpg

B) libraryによってNVDIMMの揮発メモリのように使う

(元)PMDKのlibvmemのように、ライブラリレベルでDAXとしてわりあてたNVDIMMの領域を揮発メモリ的に使う方法です。当然、これらのライブラリを使わないと揮発メモリとしては使えないので、ミドルウェアやアプリがこのライブラリに対応している必要があります。このため、これを使うのはちょっと面倒です。

そこで登場したのがDaveさんのパッチです。

C) kernelレイヤでNVDIMMを揮発メモリとして見せてくれる

これによると、Device DAXとしてフォーマットした領域をいったん切り離して、それをMemory hotaddしてRAMとして見せるようにしているようです。これによりNVDIMMがユーザからは少し遅いRAMとして見えるようになります。

NVDIMM_volatile_patch.jpg

この方式の長所は以下があげられます。
* 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/bind

2. 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年の動向にも期待しましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンボリックリンクと遊んだメモ

概要

シンボリックリンクに遊ばれたのでメモ。

シンボリックリンクが中に入っちゃう

以下実行した時、

$ ln -s /home/dir dir 

dir ディレクトリが既に存在している時に実行すると、 dir の配下に /home/dir のシンボリックリンクが生成されてしまう。

シンボリックリンクの削除

rm -rf dir では削除出来ない。
シンボリックリンクはディレクトリではないからです。

$ rm dir

でもこちらを使いたい( rm はおっかないので)。

$ unlink dir

シンボリックリンク元ディレクトリよりも上にある.htaccess

無視されるそうです。

先に知れたため、未遂に終わりました。

おわり

デプロイ、CI環境の構築を通してシンボリックリンクに遊ばれました。
ひやひやした場面もあったので、なんとなくでコマンド打たないよう気をつけようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【unzip】zipファイル内のディレクトリ区切り文字がバックスラッシュなものから任意のファイルのみを解凍する方法

もしかすると当たり前のことかもしれませんが、フィーリングで調べても答えがなかなか見つからなかったので備忘目的でメモします。

事の起こり

unzipコマンドで特定のファイルのみを解凍したい時

下記のようなzipファイルがある場合

hoge.zip
$ zipinfo -1 hoge.zip
aaa/bbb/ccc.txt
ddd/eee/fff.jpg
ggg/hhh/iii.log

unzip {解凍対象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

こうやってまとめてみると、これって常識なんじゃないかと反省し始めています……

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 パー Scissors

if program_hand==0
    program="rock"
elsif program_hand==1
    program="paper"
elsif program_hand==2
    program="scissors"
end
if player=="rock"
    player_hand=0
elsif player=="paper"
    player_hand=1
elsif player=="scissors" 
    player_hand=2
end

5.じゃんけんの勝敗をつける

ここで、条件分岐が登場
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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドキュメントから学ぶiptables

以前に何度かiptablesを理解しようと試みて挫折しました。言葉がいつもわかりません。

  • テーブルって何ですか?
  • チェインって何ですか?
  • ターゲットって何ですか?

インターネットにはたくさんの説明がありますが、どうも腑に落ちませんでした。
なので、今回は本腰入れてiptablesのドキュメントを読んで理解したいと思います。

2019.12.25 追記
iptablesを理解するために情報収集をする中で、素晴らしいリファレンスの日本語訳サイトを見つけました。
正確な知識をつけるために以下を参照することをおすすめします。
Iptablesチュートリアル 日本語訳
テーブルとチェインの概要をつかむのであれば、Chapter 6を見るのがよいと思います。

アウトライン

  1. まずiptableのマニュアルのDESCRIPTION, TAGETS, TABLESを読み、基礎知識を学び整理します。
  2. その後、@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.

  • 各チェインはパケットの集合にマッチするルールのリストである。
  • 各ルールはマッチしたパケットに何をするか指定する。
  • この「何をするか」をターゲットと呼ぶ。
    (同じテーブル内のユーザ定義のチェインへのジャンプということもある。

まとめ

  1. テーブルはLinux kernelに存在するパケットフィルターのテーブルを指す
    テーブルは複数存在し、各テーブルがチェインを含む
  2. チェインはパケットをマッチさせるルールのリスト
  3. ターゲットはマッチしたパケットに何をするか。

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テーブルの図解

ここまでの理解を図にしてみます。

Screenshot from 2019-12-24 11-16-04.png

  • 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 target

INPUTチェインにルールを追加しています。
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を見るのがよいと思います。

この記事がもしあなたの理解の助けになったら幸いです。
「いいね」よろしくお願いします。:wink:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby数字出力 0埋め

Rubyでの計算結果を0埋めで表示(3→003)

今回、例として、入力された数値の倍の数を三桁の0埋めで考える。
1→002
2→004
6→012

1.まず普通の表示

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 number3

1→002
2→004
6→012

が確認できた

参考文献

http://osishow3.hateblo.jp/entry/2017/04/24/235500

https://www.setoya-blog.com/entry/2013/12/06/171143

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

memo

Linuxコマンド

・操作しているディレクトリを表示するコマンド

$ pwd

[vagrant@localhost ~]$ pwd
/home/vagrant
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/minmin

doasのコンパイルとインストール (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/minmin

doasと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を導入したのはは正解だったと考えています。


  1. 正直筆者もsudoの全機能は理解できていません。 

  2. sudoを信頼していないわけでは無いので誤解なきよう 

  3. 実際にsudoのコア部分のソースがこの範囲だけなのかどうかまでは確認していません。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む