Rustを使ってコンテナの実装を学ぶ (UID/GID の設定)
Rustを使ってコンテナの実装を学ぶの続きをやっていきたいと思います。
実行環境(再掲)
GCPでVMインスタンスを1台起動させて、実行環境として利用しました。
$ lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 10 (buster) Release: 10 Codename: buster $ arch x86_64
UID/GID の設定
設定する前のUID/GIDを確認すると、nobody/nogroupとなっています。
$ id uid=26038 gid=65534(nogroup) groups=65534(nogroup)
これは CLONE_NEWUSERフラグ
を付与してclone(2)を実行したことで user namespaceが分離されたからです。
/proc/[pid]/uid_map と /proc/[pid]/gid_mapへ書き込みを行うことで、namespace内外のuser間でのmappingを設定できます。 (参考)
UID/GIDを0(root)に変更してみたいと思います。
実装
/proc/[pid]/uid_map と /proc/[pid]/gid_mapに書き込みを行う関数を作成しました。
write_id_mapping
でmappingフォーマットを定義しています。
フォーマット: ns内のID ns外のID length
write_file
でファイル書き込みを行います。
fn write_id_mapping( container_id: u32, host_id: u32, length: u8, map_file: &str, ) -> Result<()> { let mapping = format!("{} {} {}", container_id,host_id,length); write_file(map_file,mapping)?; Ok(()) } fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { let path = path.as_ref(); fs::write(path,contents).with_context(|| format!("failed to write to {:?}",path))?; Ok(()) }
子プロセスを生成した後に、mapping処理を実装しました。
let child_pid = sched::clone(cb, stack, flags, Some(Signal::SIGCHLD as i32)).expect("failed to create child process"); let root_id = 0; let uid_map_file : &str = &format!("/proc/{}/uid_map",child_pid); let host_uid = unsafe { libc::getuid() }; if let Err(err) = write_id_mapping(root_id,host_uid, 1,uid_map_file) { eprintln!("UID mapping failed: {}",err) } let host_gid = unsafe { libc::getgid() }; let gid_map_file : &str = &format!("/proc/{}/gid_map",child_pid); if let Err(err) = write_id_mapping(root_id,host_gid, 1,gid_map_file) { eprintln!("GID mapping failed: {}",err) }
if let Err(err) = ...
は Goでいう if err := func(); err != nil { ... }
と同じようなことが出来ます。
より短くかけるので良きです。
ビルドして実行してみたところ、uid/gidが0となりました!
$ id uid=0(root) gid=0(root) groups=0(root)