User NamespaceでrootになってNetwork Namespaceを作りvethとNATで外と通信する
linuxLinuxのNamespaceはuidやpid、networkなどを分離できる機能で、Dockerなどのコンテナ技術で使われている。
# Amazon Linux 2 (ami-0ff21806645c5e492)
$ uname -r
4.14.138-114.102.amzn2.x86_64
User NamespaceでRootになる
rootでないと正常終了しないコードを書いた。
$ cat /tmp/root_only.sh
#!/bin/sh
if [ "$(id -u)" != "0" ]; then
echo "you are not root..."
exit 1
fi
echo "you are root!"
実際ec2-userではexit 1
になる。
$ id -u
1000
$ sh /tmp/root_only.sh; echo $?
you are not root...
1
User Namespaceを作る。rootでないとそれ以外のNamespaceは作れない。
$ unshare --net
unshare: unshare failed: Operation not permitted
$ unshare --user
$ id
uid=65534(nfsnobody) gid=65534(nfsnobody) groups=65534(nfsnobody)
$ echo $$
3537
他のshellを開き、Namespaceの外側からuid_mapを書き込む。 外側の1000から始まるuidを、このNamespaceの0から始まるuidに範囲1でマッピングする。
# Namespace外
$ echo '0 1000 1' > /proc/3537/uid_map
要するに、uid=1000だったec2-userは、このNamespaceではuid=0(root)になる。上のコードも正常終了した。
$ id -u
0
$ sh /tmp/root_only.sh
you are root!
0
権限昇格してしまったように見えるかもしれないが、もちろんそんなことはなくて元々のユーザーのパーミッションでできなかったことはできない。
$ touch /root_power
touch: cannot touch '/root_power': Permission denied
Namespace内で作ったファイルをNamespace外で見ると、元々のユーザーで作ったことになっている。
$ touch /home/ec2-user/fake_root_power
$ ls -l /home/ec2-user/
total 0
-rw-rw-r-- 1 root nfsnobody 0 Sep 5 11:48 fake_root_power
# Namespace外
$ ls -l /home/ec2-user/
total 0
-rw-rw-r-- 1 ec2-user ec2-user 0 Sep 5 11:48 fake_root_power
Network Namespaceを作りvethでNamespace外と通信できるようにする
ではrootだからといって特に何もできないのかというとそんなことはなく、User以外のNamespaceが作れる。
Namespace外のネットワークはこんな感じ。
$ 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP group default qlen 1000
link/ether 06:fa:1d:4e:ec:92 brd ff:ff:ff:ff:ff:ff
inet 172.31.14.149/20 brd 172.31.15.255 scope global dynamic eth0
valid_lft 3449sec preferred_lft 3449sec
inet6 fe80::4fa:1dff:fe4e:ec92/64 scope link
valid_lft forever preferred_lft forever
$ ip route show table main
default via 172.31.0.1 dev eth0
169.254.169.254 dev eth0
172.31.0.0/20 dev eth0 proto kernel scope link src 172.31.14.149
Namespaceを作るとloopbackしかアドレスが表示されなくなり、ルートテーブルが空になる。
$ unshare --net
$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ ip r
(empty)
$ curl 8.8.8.8
curl: (7) Couldn't connect to server
$ echo $$
1011
2つのインタフェース間で通信できるvethのインタフェースを作成し、一つはNamespaceの中に移動する。sudoが必要。
# Namespace外
$ sudo ip link add name veth0-host type veth peer name veth0-ct
$ ip link
...
3: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b2:09:b8:a4:65:3d brd ff:ff:ff:ff:ff:ff
4: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 4e:89:1e:aa:84:ec brd ff:ff:ff:ff:ff:ff
$ sudo ip addr add 192.168.10.1/24 dev veth0-host
$ sudo ip link set up veth0-host
$ sudo ip link set veth0-ct netns 1011
インタフェースを立ち上げるとルートテーブルが更新され、Namespace内外が通信できるようになる。
$ ip addr add 192.168.10.2/24 dev veth0-ct
$ ping 192.168.10.1
connect: Network is unreachable
$ ip link set up veth0-ct
$ ip r
192.168.10.0/24 dev veth0-ct proto kernel scope link src 192.168.10.2
$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: veth0-ct§if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether b2:09:b8:a4:65:3d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.10.2/24 scope global veth0-ct
valid_lft forever preferred_lft forever
inet6 fe80::b009:b8ff:fea4:653d/64 scope link
valid_lft forever preferred_lft forever
$ ping 192.168.10.1
PING 192.168.10.1 (192.168.10.1) 56(84) bytes of data.
64 bytes from 192.168.10.1: icmp_seq=1 ttl=255 time=0.027 ms
64 bytes from 192.168.10.1: icmp_seq=2 ttl=255 time=0.031 ms
NATしてインターネットと通信できるようにする
まだインターネットにはルーティングされていないので通信できない。
$ ping 8.8.8.8
connect: Network is unreachable
Namespace外でIPマスカレードする。
# Namespace外
$ sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward
$ sudo iptables --table nat --flush
$ sudo iptables --table nat --append POSTROUTING --source 192.168.10.0/24 --jump MASQUERADE
$ sudo iptables --table nat --list
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- ip-192-168-10-0.ap-northeast-1.compute.internal/24 anywhere
デフォルトゲートウェイをNamespace外のvethにするとインターネットと通信できるようになる。
$ ip route add default via 192.168.10.1
$ ip r
default via 192.168.10.1 dev veth0-ct
192.168.10.0/24 dev veth0-ct proto kernel scope link src 192.168.10.2
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=41 time=2.70 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=41 time=2.78 ms
参考
第6回 Linuxカーネルのコンテナ機能[5] ─ネットワーク:LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社
第16回 Linuxカーネルのコンテナ機能 [6] ─ユーザ名前空間:LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社