User NamespaceでrootになってNetwork Namespaceを作りvethとNATで外と通信する

(2019-09-06)

Linuxの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 … 技術評論社

LinuxのNetns/veth/Bridge/NATで仮想ネットワーク構築 - kamijin-fanta