目標
在 Linux 系統上,實務上可能要調整 net
或 kernel
等參數,而要修改這些參數需要提權。在 Kubernetes 上則需要使用 Security Context
,其目的是限制容器不要非法操作影響節點。這些設定允許維運貨管理者控制容器運行時環境的安全面。這在多租戶叢集環境中至關重要,因為不同使用者的工作負載之間的隔離可以減少影響或攻擊面。在 Pod 資源上有兩種級別
接下來將探討 Security Context
細節部分。上一章節將 Quarkus 部署至 Kubernetes 環境中,且服務可以順利運行。然而以開發者角度來看其實可能不會注重安全性上的配置,以 Quarkus 的 Kubernetes 依賴來說,它並沒有為服務產生一個預設安全性即 Security Context
配置,想當然這就比較不會被注意。
部署到 Kubernetes 上的 Quarkus 服務權限到底是如何 ? 首先,透過 exec
方式進到 Pod 中的容器。
$ kubectl exec ithome2024lab-74959699f7-zq5gx -it /bin/bash
下面的操作顯示了當前 Pod 是否以 root 身分執行且是否具有一些 CAP 相關的操作能力。看到之後發現其權限特別的大,這會有一定的風險,即攻擊面範圍廣。
bash-4.4# id
uid=0(root) gid=0(root) groups=0(root)
bash-4.4# capsh --print
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
Current IAB: !cap_dac_read_search,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_ptrace,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_lease,!cap_audit_control,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read,!cap_perfmon,!cap_bpf,!cap_checkpoint_restore
Securebits: 00/0x0/1'b0 (no-new-privs=0)
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: UNCERTAIN (0)
bash-4.4#
到這繼續先探討 Security Context
,從上面的介紹來看,其有 Pod 層級和容器層級的配置。下面列出個別對應的配置內容,如果是有描述即應用上有特別配置過或是特別看過其功能。更詳細的內容可參考官方 security-context 介紹。
Pod Security Context
net.ipv4.tcp_tw_recycle
等Container Security Context
/proc
檔案系統存取限制兩者都可
隨著 Kubernetes 不同版本的發布,其內容有可能是增加或是刪除。
將 Quarkus 的 Deployment
資源新增以下 SecurityContext
欄位配置。
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
app.quarkus.io/quarkus-version: 3.13.3
app.quarkus.io/commit-id: 1935a62ca5ef3e7242ba80b9ee3f27ed08d875b4
app.quarkus.io/build-timestamp: 2024-08-24 - 10:11:15 +0000
labels:
app.kubernetes.io/name: ithome2024lab
app.kubernetes.io/version: 1.0.0-SNAPSHOT
app.kubernetes.io/managed-by: quarkus
name: ithome2024lab-day04
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ithome2024lab-day04
app.kubernetes.io/version: 1.0.0-SNAPSHOT
template:
metadata:
annotations:
app.quarkus.io/quarkus-version: 3.13.3
app.quarkus.io/commit-id: 1935a62ca5ef3e7242ba80b9ee3f27ed08d875b4
app.quarkus.io/build-timestamp: 2024-08-24 - 10:11:15 +0000
labels:
app.kubernetes.io/managed-by: quarkus
app.kubernetes.io/name: ithome2024lab-day04
app.kubernetes.io/version: 1.0.0-SNAPSHOT
namespace: default
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: registry.hub.docker.com/cch0124/ithome2024lab:1.0.0-SNAPSHOT
imagePullPolicy: Always
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
add:
- CAP_SYS_ADMIN
drop:
- ALL
livenessProbe:
failureThreshold: 3
httpGet:
path: /q/health/live
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
name: ithome2024lab
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /q/health/ready
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
startupProbe:
failureThreshold: 3
httpGet:
path: /q/health/started
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
使用 Quarkus 提供的依賴來設定,可以如下。但有些欄位是無提供設定。
quarkus:
kubernetes:
namespace: default
security-context:
run-as-user: 1000
run-as-group: 1000
run-as-non-root: true
fs-group: 2000
透過 kubectl apply
建置後,查看 Log 其實會有錯。即對於 /tmp/vertx-cache
讀寫權限不夠。
$ kubectl apply -f kubernetes/kubernetes-local.yml
$ kubectl logs -f ithome2024lab-day04-bd6587c6f-nw2nl
Aug 25, 2024 6:00:28 AM io.quarkus.vertx.core.runtime.VertxCoreRecorder
WARN: Unable to create Vert.x cache directory : /tmp/vertx-cache
Aug 25, 2024 6:00:28 AM io.quarkus.vertx.core.runtime.VertxCoreRecorder
WARN: Unable to make the Vert.x cache directory (/tmp/vertx-cache) world readable and writable
Aug 25, 2024 6:00:28 AM io.quarkus.runtime.ApplicationLifecycleManager run
如果將 readOnlyRootFilesystem
改成 false
即可正常運行,如下驗證。
$ kubectl port-forward deployment/ithome2024lab-day04 8080
$ curl http://localhost:8080/hello
Hello RESTEasy hello
readOnlyRootFilesystem
表示容器的 root 檔案系統是只讀的,即容器內的進程一般不能直接修改檔案系統。這邊嘗試使用 EmptyDir Volume 方式來解決上面問題,而非設定 readOnlyRootFilesystem
。如下
apiVersion: apps/v1
kind: Deployment
metadata:
....
name: ithome2024lab-day04
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ithome2024lab-day04
app.kubernetes.io/version: 1.0.0-SNAPSHOT
template:
....
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
volumes: # 這邊
- name: vertx-cache
emptyDir: {}
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: registry.hub.docker.com/cch0124/ithome2024lab:1.0.0-SNAPSHOT
volumeMounts: # 這邊
- name: vertx-cache
mountPath: /tmp/vertx-cache
imagePullPolicy: Always
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
...
此時我們 exec
至容器內部,可以發現使用這不在是 root
,而且也無法隨意建立檔案。這樣的設定讓攻擊範圍面縮小很多,實務上非常建議配置。
$ kubectl exec -it ithome2024lab-day04-b4547f754-hjqrv -it /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
bash-4.4$ id
uid=1000 gid=1000 groups=1000,2000
bash-4.4$ pwd
/work
bash-4.4$ capsh --print
Current: =
Bounding set =
Ambient set =
Current IAB: !cap_chown,!cap_dac_override,!cap_dac_read_search,!cap_fowner,!cap_fsetid,!cap_kill,!cap_setgid,!cap_setuid,!cap_setpcap,!cap_linux_immutable,!cap_net_bind_service,!cap_net_broadcast,!cap_net_admin,!cap_net_raw,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_chroot,!cap_sys_ptrace,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_mknod,!cap_lease,!cap_audit_write,!cap_audit_control,!cap_setfcap,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read,!cap_perfmon,!cap_bpf,!cap_checkpoint_restore
Securebits: 00/0x0/1'b0 (no-new-privs=1)
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=1000(???) euid=1000(???)
gid=1000(???)
groups=1000(???),2000(???)
Guessed mode: UNCERTAIN (0)
bash-4.4$ stat -c "%u %g" /proc/1/
1000 1000
看看這效果挺不錯的。安全人人有責。
預設情況下,容器以 root 身分執行,該身分基本上有完全管理權。這是一個安全風險,畢竟任何想利用容器的進程都有機會獲取本機系統的 root 存取權。因此可以透過 securityContext
來配置特定的使用者與群組,範例指定 1000:1000。
是一組可授予 Linux 系統中進程的權限。如果想為容器提供 ping 操作功能可以新增 CAP_NET_RAW
權限,又或者想防止更改檔案的擁有者,可以刪除 CAP_CHOWN
權限。本範例是把全部權限拔掉,畢竟該服務單純提供 API 並沒有要求底層資源。
指定了一個群組 ID,應該與容器掛載的任何卷(volume)相關聯。這允許該組讀取和寫入卷,即使卷的檔案系統本身不支持 POSIX 所有權或權限。白話一點是 fsGroup
就像是一個通行證,告訴容器它可以以某個特定身份即組 ID來訪問卷。有了這個身份,容器就能像檔案系統的"主人"一樣對卷進行操作。以本範例來看 ID 設定 2000,同時有掛載一個卷即 vertx-cache,在這 Pod 中運行的容器就可使用群組 2000 來存去這個卷,從下面結果可以知道,並對卷中的檔案進行讀寫操作。
bash-4.4$ ls -l /tmp/
total 4
drwxrwsrwx 2 root 2000 4096 Aug 25 09:44 vertx-cache
bash-4.4$ id
uid=1000 gid=1000 groups=1000,2000
root 檔案系統是否應該是唯讀。如果是,則容器將無法寫入其 root 檔案系統。建至 Image 時就定義了容器的 root 檔案系統,像是 /etc
或 /var
等。基本上可防止檔案被修改。
bash-4.4$ mkdir test
mkdir: cannot create directory 'test': Read-only file system
即進程是否可以獲得比父進程更多的權限。如果設定是 false
,則容器中的進程將無法獲得額外的權限,從而有效地防止權限提升攻擊。實驗範例設定為 false
。
用來控制容器是否以特權模式運行。當為 true 時,容器內的進程將獲得與本機相同的權限,即 root 權限,使容器內的進程可以執行任何操作。
到這邊,基本上熟悉了部分的 SecurityContext
設置。但是,對於內部團隊要如何規範這些內容是值得思考,可能是使用 OPA 或是 Kyverno 等進行全面控管。