阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

保障K8s部署中的安全性

75次阅读
没有评论

共计 12363 个字符,预计需要花费 31 分钟才能阅读完成。

导读 开发人员需要了解如何在容器化应用程序中嵌入控件,以及如何启用运行时保护机制以阻止黑客访问容器化系统。

保障 K8s 部署中的安全性

Kubernetes 是当前流行和常用的容器编排工具之一。Kubernetes 工作负载就像简单的 nginx 服务器或 cron 作业一样执行的应用程序。Kubernetes 部署成为了一种最常用的工作负载,因为它可以轻松更新、扩展和管理。

最近发布的 Kubernetes Hardening 指南是一个很好的资源,它提供了有关如何有效保护 Kubernetes 的指导。该指南提供的信息清楚地表明,保护和强化 Kubernetes 不仅是 Kubernetes 管理员的工作,也是在集群上部署工作负载的开发人员的工作。

本文讨论部署 Kubernetes 工作负载的开发人员如何通过应用“Kubernetes 强化指南”提供的一些指南来引导安全性。

这是一个实用的指南,将采用一个简单的 Dockerfile,然后逐步添加安全优秀实践来创建模板部署清单文件,开发人员可以快速重用该文件。

先决条件
    Docker 是必需的,因为将从头开始构建。
    像 minikube 这样的单节点 Kubernetes 集群应该遵循这一指南以及 kubectl 实用程序。开发人员可以使用官方 minikube 文档在其环境中进行设置。

使用由与 WSL2 绑定的 Docker Desktop 创建的独立集群作为后端。

本指南假设有一个可通过 kubectl 实用程序访问的正在运行的集群,如下面的代码所示。

Shell 
git clone git@github.com:salecharohit/bootstrapsecurityinkubernetesdeployment.git 
cd springbootmaven 
docker build . -f Dockerfile.basic -t springbootmaven 
docker run --name springboot -d -p 8080:8080 springbootmaven 
curl http://localhost:8080 
 
​Expected Response: 
 
Hello World From Spring Boot Build Using Maven on Alpine OS!
安全部署

保护 Kubernetes 工作负载可以有效地划分为“构建时”的安全性和“运行时”的安全性。为了运行这些示例,将使用这个简单的 Spring Boot Hello World 应用程序并将其部署在 Kubernetes 中,并应用构建时安全性和运行时安全性。相关网址如下:https://github.com/salecharohit/bootstrapsecurityinkubernetesdeployment

而在开始之前,先要克隆这个存储库,构建 Docker 容器,并在本地运行应用程序。

构建时的安全性

构建时的安全性更多地关注如何以减少的占用空间构建底层容器,并编程以尽可能少的权限执行。

以下将使用问题的解决方法讨论这两种方法:

(1)减少攻击面

在容器中构建应用程序时,主要目标是让应用程序在不考虑运行环境的情况下始终独立运行,无论其运行环境是数据中心、云平台还是内部部署设施。然而,在构建这些应用程序时有一条不成文的规则:它应该是一个独立的应用程序,并且没有很多依赖项。

以 SpringBoot 应用程序为例。这个应用程序运行的唯一依赖是它需要一个 JVM 或 Java 运行时。任何其他在容器中的东西实际上都是无用的。

例如,在基于 AlpineOS 构建的 SpringBoot 容器中,没有任何特定需要安装 apk 包管理器。

Shell  
docker exec -it springboot /bin/sh 
apk add curl

因此,可以尝试删除 apk 二进制文件并重建或 Docker 映像。

此时将使用 Dockerfile.asr 来重建 Docker 容器,其共享如下:

Dockerfile  
FROM maven:3.8.1-openjdk-17-slim AS MAVEN_BUILD 
WORKDIR /build/ 
COPY pom.xml /build/ 
COPY src /build/src/ 
RUN mvn package 
 
FROM openjdk:17-alpine 
 
RUN rm -f /sbin/apk && \ 
rm -rf /etc/apk && \ 
rm -rf /lib/apk && \ 
rm -rf /usr/share/apk && \ 
rm -rf rm -rf /var/lib/apk 
 
COPY --from=MAVEN_BUILD /build/target/springbootmaven.jar /springbootmaven.jar 
EXPOSE 8080 
CMD java -jar /springbootmaven.jar

在此重建并重新运行:

Shell  
# First let's stop the previously running container 
docker stop springboot  
# Next let's re-build and re-run 
docker build . -f Dockerfile.asr -t springbootmaven  
docker run --name springboot -p 8080:8080 springbootmaven  
docker run --name springboot -d -p 8080:8080 springbootmaven  
curl http://localhost:8080

现在尝试再次运行 apk add curl 命令。

Shell  
docker exec -it springboot /bin/sh  
apk add curl

因此成功摆脱了 apk 依赖,并且应用程序运行成功!

下面是一些专门为强化 Alpine OS 编写的优秀脚本。根据编程语言进行挑选,并相应地强化基本 alpine 图像。以下是一些参考的网址:

https://gist.github.com/kost/017e95aa24f454f77a37
https://github.com/ironpeakservices/iron-alpine/blob/master/Dockerfile
另一方面,还可以查看由谷歌公司创建的 distroless 容器,这也是非常值得推荐的。

https://github.com/GoogleContainerTools/distroless/tree/main/examples

(2)切换用户场景

有人可能会争辩说,如果网络攻击者在容器内获得 RCE,他们可能无法安装 curl、wget 等包来建立持久性。

但是,仍然以“root”用户身份运行,从技术上讲,仍然可以重新安装 apk。

在此重新运行 Docker 容器并检查它当前运行的权限。

Shell  
docker exec -it springboot /bin/sh  
whoami  
ping rohitsalecha.com

因此,重要的是不要以 root 身份运行容器,而是以只有有限权限的用户身份运行容器。

Dockerfile.lpr 显示了添加更多命令,这些命令添加了一个名为“boot”的用户和组,并为其分配一个工作目录(这是它的主目录)。还为用户和组分配了数值,以下将在 Pod 安全场景部分详细讨论。

Dockerfile  
FROM maven:3.8.1-openjdk-17-slim AS MAVEN_BUILD 
WORKDIR /build/ 
COPY pom.xml /build/ 
COPY src /build/src/ 
RUN mvn package 
 
FROM openjdk:17-alpine 
 
# Removing apk package manager 
RUN rm -f /sbin/apk && \ 
rm -rf /etc/apk && \ 
rm -rf /lib/apk && \ 
rm -rf /usr/share/apk && \ 
rm -rf rm -rf /var/lib/apk 
 
# Adding a user and group called "boot" 
RUN addgroup boot -g 1337 && \  
adduser -D -h /home/boot -u 1337 -s /bin/ash boot -G boot 
 
# Changing the context that shall run the below commands with User "boot" instead of root 
USER boot 
WORKDIR /home/boot 
 
# By default even in a non-root context, Docker copies the file as root. Hence its best practice to chown 
# the files being copied as the user. https://stackoverflow.com/a/44766666/1679541 
COPY --chown=boot:boot --from=MAVEN_BUILD /build/target/springbootmaven.jar /home/boot/springbootmaven.jar 
EXPOSE 8080 
CMD java -jar /home/boot/springbootmaven.jar

重建并重新运行:

# First let's stop the previously running container 
docker stop springboot  
# Next let's re-build and re-run 
docker build . -f Dockerfile.lpr -t springbootmaven docker run --name springboot -d -p 8080:8080 springbootmaven curl http://localhost:8080

现在尝试运行 whoami 命令,并检查现在正在运行哪个容器的哪些权限。

Shell  
docker exec -it springboot /bin/sh  
whoami  
ping rohitsalecha.com 
运行时的安全性

现在人们对构建时安全性有了很大的信心,其中已经学会了删除包并更新用户场景,以使用有限的权限运行容器。这些安全特性是在构建 Docker 容器时应用的; 但是,还需要关注容器在 Kubernetes 环境中运行时的安全状况,这将在下面进行探讨。

在开始保护 Kubernetes 部署之前,首先将 Docker 容器推送到 hub.docker.com,在 Kubernetes 集群上运行的应用程序。可以使用这一指南开始相同的操作。

Shell  
docker build . -f Dockerfile.lpr -t springbootmaven 
docker tag springbootmaven salecharohit/springbootmaven 
docker push salecharohit/springbootmaven 
docker run -d -p 8080:8080 --name springboot salecharohit/springbootmaven 
curl http://localhost:8080

现在 Docker 镜像已经准备好了,应用 kubernetes-basic.yaml 文件来部署这个应用程序以及一个可以帮助连接到它的服务。

YAML  
# Create Namespace 
apiVersion: v1 
kind: Namespace 
metadata: 
 name: boot 
 
--- 
# Create SpringBoot Deployment 
apiVersion: apps/v1 
kind: Deployment 
metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
replicas: 1 
selector: 
matchLabels: 
app: springbootmaven 
template: 
metadata: 
labels: 
 app: springbootmaven 
spec: 
containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
 ports: 
- containerPort: 8080 
 
--- 
# Create Service for SpringBoot Deployment 
apiVersion: v1 
kind: Service 
metadata: 
 labels: 
 app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
ports: 
- name: "http" 
port: 8080 
targetPort: 8080 
selector: 
app: springbootmaven

如果 Pod 需要与 Kubernetes API-Server 通信,则需要服务帐户令牌进行身份验证。

Shell  
kubectl apply -f kubernetes-basic.yaml  
kubectl get deploy -n boot 
# Run a temporary container that will only curl our bootservice 
kubectl run -it testpod --image=radial/busyboxplus:curl --restart=Never --rm -- curl http://springbootmaven.boot.svc.cluster.local:8080  
 
​Expected Output:  
Hello World From Spring Boot Build Using Maven on Alpine OS!pod "testpod" deleted
(1)服务帐户令牌

如果 Pod 需要与 Kubernetes API 服务器通信,则需要服务帐户令牌进行身份验证。

在默认情况下,每个 Pod 都会分配一个服务帐户令牌,该令牌安装在 /var/run/secrets/kubernetes.io/serviceaccount/token 上。可以通过部署 SpringBoot 应用程序在实践中查看这一点。

Shell  
kubectl get pods -n boot 
kubectl exec -it springbootmaven-7d7c5c8597-mndv9 -n boot -- /bin/sh 
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) 
curl -k -H "Authorization:Bearer $TOKEN" https://kubernetes.docker.internal:6443/version

应用程序上的 RCE 漏洞可以将此访问令牌泄露给网络攻击者,他们可以滥用该令牌来读写同一命名空间中的资源,甚至具有全局读取权限。

解决这一问题有两个解决方案,具体取决于具体情况:一是 Pod 不需要访问 API-Server。二是 Pod 需要访问 API-Server。

    不需要访问 API-Server 的 Pod

这种情况很容易解决,只需在 Kubernetes 清单文件中添加两行,如下所示:

YAML  
1 serviceAccountName: "" 
2 automountServiceAccountToken: false

完整的部署文件 kubernetes-nosa.yaml 如下:

YAML  
apiVersion: apps/v1 
kind: Deployment 
metadata: 
labels: 
app: springbootmaven 
 name: springbootmaven 
namespace: boot 
spec: 
eplicas: 1 
selector: 
matchLabels: 
app: springbootmaven 
template: 
metadata: 
labels: 
app: springbootmaven 
spec: 
containers: 
image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
- containerPort: 8080 
serviceAccountName: "" 
automountServiceAccountToken: false

然后检查服务帐户令牌现在是否已安装。

Shell  
# Ensure our previous deploy is deleted.  
kubectl delete ns boot 
 
# Apply with no service account token 
kubectl apply -f kubernetes-nosa.yaml 
kubectl get pods -n boot 
kubectl exec -it springbootmaven-5568b9874f-8nml8 -n boot -- /bin/sh 
cat /var/run/secrets/kubernetes.io/serviceaccount/token

如上所示,不再挂载默认服务帐户令牌。

    需要访问 API-Server 的 Pod

在这种情况下,需要创建将 ServiceAccount 映射到角色的 ServiceAccount、Role 和 RoleBinding。以下是 Kubernetes 清单:

    为特定命名空间 (即 boot) 创建一个名为 bootserviceaccount 的 ServiceAccount。
    创建一个名为 bootservicerole 的角色,该角色只有查看正在运行的 Pod 的权限。
    创建一个名为 bootservicerolebinding 的 RoleBinding。
    挂载 ServiceAccount,从而在部署中使用以下几行进行创建。
YAML  
--- 
spec: 
containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
- containerPort: 8080 
 serviceAccountName: bootserviceaccount 
---

这将允许仅读取“boot”命名空间中的 Pod。

完整的部署文件 kubernetes-withsa.yaml 如下:

YAML  
# Create Namespace 
apiVersion: v1 
kind: Namespace 
metadata: 
name: boot 
 
--- 
apiVersion: v1 
kind: ServiceAccount 
metadata: 
name: bootserviceaccount 
namespace: boot 
 
--- 
kind: Role 
apiVersion: rbac.authorization.k8s.io/v1 
metadata: 
name: bootservicerole 
namespace: boot 
rules: 
- apiGroups: [""] 
resources: ["pods"] 
verbs: ["get", "list", "watch"] 
 
--- 
kind: RoleBinding 
apiVersion: rbac.authorization.k8s.io/v1 
metadata: 
name: bootservicerolebinding 
namespace: boot 
subjects: 
- kind: ServiceAccount 
name: bootserviceaccount 
namespace: boot 
roleRef: 
kind: Role 
name: bootservicerole 
apiGroup: rbac.authorization.k8s.io 
 
--- 
# Create SpringBoot Deployment 
apiVersion: apps/v1 
kind: Deployment 
metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
replicas: 1 
selector: 
matchLabels: 
app: springbootmaven 
template: 
metadata: 
labels: 
app: springbootmaven 
spec: 
containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
 - containerPort: 8080 
serviceAccountName: bootserviceaccount 
 
--- 
# Create Service for SpringBoot Deployment 
apiVersion: v1 
kind: Service 
metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
ports: 
 - name: "http" 
port: 8080 
targetPort: 8080 
selector: 
app: springbootmaven

现在运行并检查的应用程序是否运行良好。

# Ensure our previous deploy is deleted.  
kubectl delete ns boot  
kubectl apply -f kubernetes-withsa.yaml  
kubectl run -it testpod --image=radial/busyboxplus:curl --restart=Never --rm -- curl http://springbootmaven.boot.svc.cluster.local:8080

# 确保删除以前的部署。

(2)Pod 的安全场景

尽管已将基本 Docker 映像配置为以非 root 权限运行,但仍需要添加少量配置作为安全优秀实践。因此需要:

    ①限制容器和 Pod 的能力。
    ②禁用权限提升。
    ③将容器配置为使用先前在 Dockerfile.lpr 中创建的特定 uid/gid 运行。

在 Kubernetes 清单文件中,定义了两种类型的“安全场景(SecurityContexts)”。

    在 Pod 级别运行,这将应用到在这个 Pod 中运行的所有容器
YAML  
 --- 
securityContext: 
fsGroup: 1337 
runAsNonRoot: true 
runAsUser: 1337 
containers: 
---
    在容器级别运行
YAML  
--- 
securityContext: 
allowPrivilegeEscalation: false 
privileged: false 
runAsUser: 1337 
capabilities: 
drop: ["SETUID", "SETGID"] 
serviceAccountName: "" 
automountServiceAccountToken: false 
---

嵌入 PodSecurity 场景的完整部署文件 kubernetes-ps.yaml 如下:

YAML  
# Create Namespace 
apiVersion: v1 
kind: Namespace 
metadata: 
name: boot 
--- 
# Create SpringBoot Deployment 
apiVersion: apps/v1 
kind: Deployment 
metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
replicas: 1 
selector: 
matchLabels: 
app: springbootmaven 
template: 
metadata: 
labels: 
app: springbootmaven 
spec: 
securityContext: 
fsGroup: 1337 
runAsNonRoot: true 
runAsUser: 1337 
 containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
- containerPort: 8080 
securityContext: 
allowPrivilegeEscalation: false 
privileged: false 
runAsUser: 1337 
capabilities: 
drop: ["SETUID", "SETGID"] 
erviceAccountName: "" 
automountServiceAccountToken: false 
--- 
# Create Service for SpringBoot Deployment 
apiVersion: v1 
kind: Service 
metadata: 
labels: 
 app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
ports: 
- name: "http" 
port: 8080 
targetPort: 8080 
selector: 
app: springbootmaven

运行并测试应用程序是否正在运行。

Shell  
# Ensure our previous apply is deleted 
kubectl delete ns boot  
kubectl apply -f kubernetes-ps.yaml  
kubectl run -it testpod --image=radial/busyboxplus:curl --restart=Never --rm -- curl http://springbootmaven.boot.svc.cluster.local:8080  
kubectl get pods -n boot  
kubectl exec -it springbootmaven-56c64ff85-mqz2z -n boot -- /bin/sh  
whoami  
id  
ping google.com

开发人员可以根据自己的需求删除更多功能。

AppArmor 或 SecComp 等功能需要控制平面组件的附加配置。因此,我的讨论仅限于开箱即用的功能,这些功能可以轻松激活并确保良好的安全保证水平。

(3)不可变文件系统

在容器化环境中运行的应用程序很少写入数据,因为这实际上违背了拥有不可变系统的逻辑。但是,有时可能需要它来缓存或临时交换 / 处理文件。因此,为了向开发人员提供此功能,可以将 emptyDir 装载为临时卷,一旦容器被终止,该临时卷就会丢失。

有了它,还可以添加另一个名为“readOnlyRootFilesystem”的安全场景属性,并将其设置为 true,因为在容器中运行的应用程序不再需要在文件系统上除“tmp”目录以外的任何位置写入。

可以按如下所示配置上述要求。

YAML  
--- 
containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
- containerPort: 8080 
securityContext: 
readOnlyRootFilesystem: true 
volumeMounts: 
- mountPath: /tmp 
name: tmp 
volumes: 
- emptyDir: {} 
name: tmp 
 ---

完整的部署文件 kubernetes-rofs.yaml 如下面的代码所示:

YAML  
# Create Namespace 
apiVersion: v1 
kind: Namespace 
metadata: 
name: boot 
--- 
# Create SpringBoot Deployment 
apiVersion: apps/v1 
kind: Deployment 
 metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
replicas: 1 
selector: 
matchLabels: 
app: springbootmaven 
template: 
metadata: 
labels: 
app: springbootmaven 
spec: 
securityContext: 
fsGroup: 1337 
runAsNonRoot: true 
runAsUser: 1337 
containers: 
- image: salecharohit/springbootmaven 
name: springbootmaven 
ports: 
- containerPort: 8080 
securityContext: 
allowPrivilegeEscalation: false 
readOnlyRootFilesystem: true 
privileged: false 
runAsUser: 1337 
capabilities: 
drop: ["SETUID", "SETGID"] 
volumeMounts: 
- mountPath: /tmp 
name: tmp 
serviceAccountName: "" 
automountServiceAccountToken: false 
 volumes: 
- emptyDir: {} 
name: tmp 
 
--- 
# Create Service for SpringBoot Deployment 
apiVersion: v1 
kind: Service 
metadata: 
labels: 
app: springbootmaven 
name: springbootmaven 
namespace: boot 
spec: 
ports: 
- name: "http" 
port: 8080 
targetPort: 8080 
selector: 
app: springbootmaven

开始应用并测试应用程序是否正在运行。

Shell  
# Ensure our previous apply is deleted 
kubectl delete ns boot  
kubectl apply -f kubernetes-rofs.yaml  
kubectl run -it testpod --image=radial/busyboxplus:curl --restart=Never --rm -- curl http://springbootmaven.boot.svc.cluster.local:8080  
kubectl get pods -n boot  
kubectl exec -it springbootmaven-56c64ff85-mqz2z -n boot -- /bin/sh 
pwd 
touch test.txt
结论

如今已经了解了可以在容器化应用程序中嵌入哪些不同的控件,还了解了如何启用运行时保护机制,这些机制可以使网络攻击者难以在容器化系统中站稳脚跟。

kubernetes-rofs.yaml 可以作为一个很好的模板,供开发人员在 kubernetes 环境中部署时使用默认的安全功能对其应用程序进行容器化。

当然,企业需要为特定的应用程序创建 Dockerfile。

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-25发表,共计12363字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中