User namespace用來隔離user權限相關的Linux資源,包括user IDs and group IDs,keys , 和capabilities. html
這是目前實現的namespace中最複雜的一個,由於user和權限息息相關,而權限又事關容器的安全,因此稍有不慎,就會出安全問題。linux
user namespace能夠嵌套(目前內核控制最多32層),除了系統默認的user namespace外,全部的user namespace都有一個父user namespace,每一個user namespace均可以有零到多個子user namespace。 當在一個進程中調用unshare或者clone建立新的user namespace時,當前進程原來所在的user namespace爲父user namespace,新的user namespace爲子user namespace.shell
在不一樣的user namespace中,一樣一個用戶的user ID 和group ID能夠不同,換句話說,一個用戶能夠在父user namespace中是普通用戶,在子user namespace中是超級用戶(超級用戶只相對於子user namespace所擁有的資源,沒法訪問其餘user namespace中須要超級用戶才能訪問資源)。ubuntu
從Linux 3.8開始,建立新的user namespace不須要root權限。安全
本篇全部例子都在ubuntu-server-x86_64 16.04下執行經過bash
#--------------------------第一個shell窗口---------------------- #先記錄下目前的id,gid和user namespace dev@ubuntu:~$ id uid=1000(dev) gid=1000(dev) groups=1000(dev),4(adm),24(cdrom),27(sudo) dev@ubuntu:~$ readlink /proc/$$/ns/user user:[4026531837] #建立新的user namespace dev@ubuntu:~$ unshare --user /bin/bash nobody@ubuntu:~$ readlink /proc/$$/ns/user user:[4026532464] nobody@ubuntu:~$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
很奇怪,爲何上面例子中顯示的用戶名是nobody,它的id和gid都是65534?app
這是由於咱們尚未映射父user namespace的user ID和group ID到子user namespace中來,這一步是必須的,由於這樣系統才能控制一個user namespace裏的用戶在其餘user namespace中的權限。(好比給其餘user namespace中的進程發送信號,或者訪問屬於其餘user namespace掛載的文件)ide
若是沒有映射的話,當在新的user namespace中用getuid()和getgid()獲取user id和group id時,系統將返回文件/proc/sys/kernel/overflowuid中定義的user ID以及proc/sys/kernel/overflowgid中定義的group ID,它們的默認值都是65534。也就是說若是沒有指定映射關係的話,會默認映射到ID65534。函數
下面看看這個user能幹些什麼測試
#--------------------------第一個shell窗口---------------------- #ls的結果顯示/root目錄屬於nobody nobody@ubuntu:~$ ls -l /|grep root drwx------ 3 nobody nogroup 4096 7月 8 18:39 root #可是當前的nobody帳號訪問不了,說明這兩個nobody不是一個ID,他們之間沒有映射關係 nobody@ubuntu:~$ ls /root ls: cannot open directory '/root': Permission denied #這裏顯示/home/dev目錄屬於nobody nobody@ubuntu:~$ ls -l /home/ drwxr-xr-x 11 nobody nogroup 4096 7月 8 18:40 dev #touch成功,說明雖然沒有顯式的映射ID,但仍是能訪問父user namespace裏dev帳號擁有的資源 #說明他們背後仍是有映射關係 nobody@ubuntu:~$ touch /home/dev/temp01 nobody@ubuntu:~$
一般狀況下,建立新的user namespace後,第一件事就是映射user和group ID. 映射ID的方法是添加配置到/proc/PID/uid_map和/proc/PID/gid_map(這裏的PID是新user namespace中的進程ID,剛開始時這兩個文件都是空的).
這兩個文件裏面的配置格式以下(能夠有多條):
ID-inside-ns ID-outside-ns length
舉個例子, 0 1000 256這條配置就表示父user namespace中的1000~1256映射到新user namespace中的0~256。
系統默認的user namespace沒有父user namespace,但爲了保持一致,kernel提供了一個虛擬的uid和gid map文件,看起來是這樣子的:
dev@ubuntu:~$ cat /proc/$$/uid_map
0 0 4294967295
那麼誰能夠向這個文件中寫配置呢?
/proc/PID/uid_map和/proc/PID/gid_map的擁有者是建立新user namespace的這個user,因此和這個user在一個user namespace的root帳號能夠寫。但這個user本身有沒有寫map文件權限還要看它有沒有CAP_SETUID和CAP_SETGID的capability。
注意:只能向map文件寫一次數據,但能夠一次寫多條,而且最多隻能5條
關於capability的詳細介紹能夠參考這裏,簡單點說,原來的Linux就分root和非root,不少操做只能root完成,好比修改一個文件的owner,後來Linux將root的一些權限分解了,變成了各類capability,只要擁有了相應的capability,就能作相應的操做,不須要root帳戶的權限。
下面咱們來看看如何用dev帳號映射uid和gid
#--------------------------第一個shell窗口---------------------- #獲取當前bash的pid nobody@ubuntu:~$ echo $$ 24126 #--------------------------第二個shell窗口---------------------- #dev是map文件的owner dev@ubuntu:~$ ls -l /proc/24126/uid_map /proc/24126/gid_map -rw-r--r-- 1 dev dev 0 7月 24 23:11 /proc/24126/gid_map -rw-r--r-- 1 dev dev 0 7月 24 23:11 /proc/24126/uid_map #但仍是沒有權限寫這個文件 dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map bash: echo: write error: Operation not permitted dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map bash: echo: write error: Operation not permitted #當前用戶運行的bash進程沒有CAP_SETUID和CAP_SETGID的權限 dev@ubuntu:~$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 #爲/binb/bash設置capability, dev@ubuntu:~$ sudo setcap cap_setgid,cap_setuid+ep /bin/bash #從新加載bash之後咱們看到相應的capability已經有了 dev@ubuntu:~$ exec bash dev@ubuntu:~$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 00000000000000c0 CapEff: 00000000000000c0 #再試一次寫map文件,成功了 dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map dev@ubuntu:~$ #再寫一次就失敗了,由於這個文件只能寫一次 dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/uid_map bash: echo: write error: Operation not permitted dev@ubuntu:~$ echo '0 1000 100' > /proc/24126/gid_map bash: echo: write error: Operation not permitted #後續測試不須要CAP_SETUID了,將/bin/bash的capability恢復到原來的設置 dev@ubuntu:~$ sudo setcap cap_setgid,cap_setuid-ep /bin/bash dev@ubuntu:~$ getcap /bin/bash /bin/bash = #--------------------------第一個shell窗口---------------------- #回到第一個窗口,id已經變成0了,說明映射成功 nobody@ubuntu:~$ id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) #--------------------------第二個shell窗口---------------------- #回到第二個窗口,確認map文件的owner,這裏24126是新user namespace中的bash dev@ubuntu:~$ ls -l /proc/24126/ ...... -rw-r--r-- 1 dev dev 0 7月 24 23:13 gid_map dr-x--x--x 2 dev dev 0 7月 24 23:10 ns -rw-r--r-- 1 dev dev 0 7月 24 23:13 uid_map ...... #--------------------------第一個shell窗口---------------------- #從新加載bash,提示有root權限了 nobody@ubuntu:~$ exec bash root@ubuntu:~# #0000003fffffffff表示當前運行的bash擁有全部的capability root@ubuntu:~# cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)' CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff #--------------------------第二個shell窗口---------------------- #回到第二個窗口,發現owner已經變了,變成了root #目前還不清楚爲何有這樣的機制 dev@ubuntu:~$ ls -l /proc/24126/ ...... -rw-r--r-- 1 root root 0 7月 24 23:13 gid_map dr-x--x--x 2 root root 0 7月 24 23:10 ns -rw-r--r-- 1 root root 0 7月 24 23:13 uid_map ...... #雖然不能看目錄裏有哪些文件,可是能夠讀裏面文件的內容 dev@ubuntu:~$ ls -l /proc/24126/ns ls: cannot open directory '/proc/24126/ns': Permission denied dev@ubuntu:~$ readlink /proc/24126/ns/user user:[4026532464] #--------------------------第一個shell窗口---------------------- #和第二個窗口同樣的結果 root@ubuntu:~# ls -l /proc/24126/ns ls: cannot open directory '/proc/24126/ns': Permission denied root@ubuntu:~# readlink /proc/24126/ns/user user:[4026532464] #仍然不能訪問/root目錄,由於他的擁有着是nobody root@ubuntu:~# ls -l /|grep root drwx------ 3 nobody nogroup 4096 7月 8 18:39 root root@ubuntu:~# ls /root ls: cannot open directory '/root': Permission denied #對於原來/home/dev下的內容,顯示的owner已經映射過來了,由dev變成了新namespace中的root, #當前root用戶能夠訪問他裏面的內容 root@ubuntu:~# ls -l /home drwxr-xr-x 8 root root 4096 7月 21 18:35 dev root@ubuntu:~# touch /home/dev/temp01 root@ubuntu:~# #試試設置主機名稱 root@ubuntu:~# hostname container001 hostname: you must be root to change the host name #修改失敗,說明這個新user namespace中的root帳號在父user namespace裏面很差使 #這也正是user namespace所指望達到的效果,當訪問其餘user namespace裏的資源時, #是以其餘user namespace中的相應帳號的權限來執行的, #好比這裏root對應父user namespace的帳號是dev,因此改不了系統的hostname
那是否是把系統默認user namespace的root帳號映射到新的user namespace中,新user namespace的root就能夠修改默認user namespace中的hostname呢?
#--------------------------第三個shell窗口---------------------- #從新打開一個窗口 #這裏再也不手動映射uid和gid,而是利用unshare命令的-r參數來幫咱們完成映射, #指定-r參數後,unshare將會幫助咱們將當前運行unshare的帳號映射成新user namesapce的root帳號 #這裏用了sudo,目的是讓root帳號來運行unshare命令, #這樣就將外面的root帳號映射成新user namespace的root帳號 dev@ubuntu:~$ sudo unshare --user -r /bin/bash root@ubuntu:~# id uid=0(root) gid=0(root) groups=0(root) #確認是用root映射root root@ubuntu:~# echo $$ 24283 root@ubuntu:~# cat /proc/24283/uid_map 0 0 1 root@ubuntu:~# cat /proc/24283/gid_map 0 0 1 #能夠訪問/root目錄下的東西,但沒法操做/home/dev/下的文件 root@ubuntu:~# ls -l / |grep root$ drwx------ 6 root root 4096 8月 14 23:11 root root@ubuntu:~# touch /root/temp01 root@ubuntu:~# ls -l /home drwxr-xr-x 11 nobody nogroup 4096 8月 14 23:13 dev root@ubuntu:~# touch /home/dev/temp01 touch: cannot touch '/home/dev/temp01': Permission denied #嘗試修改hostname,仍是失敗 root@ubuntu:~# hostname container001 hostname: you must be root to change the host name
上面的例子中雖然是將root帳號映射到了新user namespace的root帳號上,但修改hostname、訪問/home/dev下的文件依然失敗,那是由於無論怎麼映射,當用子user namespace的帳號訪問父user namespace的資源的時候,它啓動的進程的capability都爲空,因此這裏子user namespace的root帳號到父namespace中就至關於一個普通的帳號。
注意:對於map文件來講,在父user namespace和子user namespac中打開子user namespace中進程的這個文件看到的都是一樣的內容,但若是是在其餘的user namespace中打開這個map文件,‘ID-outside-ns’表示的就是映射到當前user namespace的ID.這裏聽起來有點繞,看下面的例子
#--------------------------打開一個新窗口---------------------- #建立一個新的user namespace,並取名container001 dev@ubuntu:~$ unshare --user --uts -r /bin/bash root@ubuntu:~# hostname container001 root@ubuntu:~# exec bash #記下bash的pid root@container001:~# echo $$ 27898 #在container001裏面建立新的namespace container002 root@container001:~# unshare --user --uts -r /bin/bash root@container001:~# hostname container002 root@container001:~# exec bash #記下bash的pid root@container002:~# echo $$ 28066 #查看本身namespace中進程的uid map文件 #這裏表示父user namespace的0映射到了當前namespace的0 root@container002:~# cat /proc/28066/uid_map 0 0 1 #--------------------------再打開一個新窗口---------------------- #在系統默認namespace中查看一樣這個文件,發現和上面的顯示的不同 #由於默認namespace是container002的爺爺,因此他們兩個裏面看到的東西有可能不同 #這裏表示當前user namespace的帳號1000映射到了進程28066所在user namespace的帳號0 #固然若是上面是用root帳號建立的container001,這裏顯示的內容就和上面同樣了 dev@ubuntu:~$ cat /proc/28066/uid_map 0 1000 1 #咱們再進入到container001,在裏面看看這個文件,發現和在ontainer002看到的結果同樣 #說明對於進程28066來講,在他本身所在的user namespace和他的父user namespace看到的map文件內容是同樣的 dev@ubuntu:~$ nsenter --user --uts -t 27898 --preserve-credentials bash root@container001:~# cat /proc/28066/uid_map 0 0 1 #默認狀況下,nsenter會調用setgroups函數去掉root group的權限, #這裏--preserve-credentials是爲了讓nsenter不調用setgroups函數,由於調用這個函數須要root權限 #測試完成後能夠關閉這兩個窗口,後面不會再用到了
當一個用戶建立一個新的user namespace的時候,這個用戶就是這個新user namespace的owner,在父user namespace的這個用戶就會擁有新user namespace及其全部子孫user namespace的全部capabilities.
#--------------------------第四個shell窗口---------------------- #新建用戶test用於測試 dev@ubuntu:~$ sudo useradd test dev@ubuntu:~$ sudo passwd test Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully #切換到test帳戶並建立新的user namespace #爲了便於區分,同時建立新的uts namespace dev@ubuntu:~$ su test Password: test@ubuntu:/home/dev$ unshare --user --uts -r /bin/bash #設置一個容易區分的hostname root@ubuntu:/home/dev# hostname container001 root@ubuntu:/home/dev# exec bash root@container001:/home/dev# readlink /proc/$$/ns/user user:[4026532463] root@container001:/home/dev# echo $$ 24419 #--------------------------第五個shell窗口---------------------- #使用dev帳號新建一個user namespace dev@ubuntu:~$ unshare --user --uts -r /bin/bash root@ubuntu:~# hostname container002 root@ubuntu:~# exec bash root@container002:~# readlink /proc/$$/ns/user user:[4026532464] root@container002:~# echo $$ 24435 #--------------------------第六個shell窗口---------------------- #用dev帳號往container002中加入新的進程/bin/bash成功,由於dev是container002的owner dev@ubuntu:~$ nsenter --user -t 24435 --preserve-credentials --uts /bin/bash root@container002:~# id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) root@container002:~# readlink /proc/$$/ns/user user:[4026532464] #回到默認user namespace root@container002:~# exit exit dev@ubuntu:~$ #由於container001的owner是test,用dev帳號往container001中加入新的進程/bin/bash失敗 dev@ubuntu:~$ nsenter --user -t 24419 --preserve-credentials --uts /bin/bash nsenter: cannot open /proc/24419/ns/user: Permission denied #用root帳號往container001中加入新的進程/bin/bash成功 dev@ubuntu:~$ sudo nsenter --user -t 24419 --preserve-credentials --uts /bin/bash nobody@container001:~$ readlink /proc/$$/ns/user user:[4026532463] #因爲root帳號沒有映射到container001中,因此這裏在container001中看到的帳號是nobody nobody@container001:~$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup) #退出container001,便於後續測試 nobody@container001:~$ exit dev@ubuntu:~$ #--------------------------第五個shell窗口---------------------- #回到第5個窗口,繼續建立一個新的user namespace root@container002:~# unshare --user --uts -r /bin/bash root@container002:~# hostname container003 root@container002:~# exec bash root@container003:~# readlink /proc/$$/ns/user user:[4026532471] root@container003:~# echo $$ 24533 #--------------------------第六個shell窗口---------------------- #回到第6個窗口,用dev帳號往container003(孫子user namespace)中加入新的bash進程,成功, #說明dev擁有孫子user namespace的capabilities dev@ubuntu:~$ nsenter --user -t 24533 --preserve-credentials --uts /bin/bash root@container003:~# readlink /proc/$$/ns/user user:[4026532471]
本文先介紹了user namespace的一些概念,而後介紹如何配置mapping文件,最後介紹了user namespace的owner。從上面的介紹中能夠看出,user namespace仍是比較複雜的,要了解user namespace,須要對Linux下的權限有一個基本的瞭解。下一篇中將繼續介紹user namespace和其餘namespace的關係,以及一些其餘的注意事項。