Linux Namespace系列(07):user namespace (CLONE_NEWUSER) (第一部分)

User namespace用來隔離user權限相關的Linux資源,包括user IDs and group IDskeys , 和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

建立user namespace

#--------------------------第一個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 ID和group ID

一般狀況下,建立新的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的owner

當一個用戶建立一個新的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的關係,以及一些其餘的注意事項。

參考

相關文章
相關標籤/搜索