|
|
|
|
公众号矩阵

在Kubernetes部署中如何引导安全性

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

作者:李睿来源:51CTO|2021-09-30 08:00

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

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

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

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

先决条件

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

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

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

  1. Shell 
  2. git clone git@github.com:salecharohit/bootstrapsecurityinkubernetesdeployment.git 
  3. cd springbootmaven 
  4. docker build . -f Dockerfile.basic -t springbootmaven 
  5. docker run --name springboot -d -p 8080:8080 springbootmaven 
  6. curl http://localhost:8080 
  7.  
  8. ​Expected Response: 
  9.  
  10. 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包管理器。

  1. Shell  
  2. docker exec -it springboot /bin/sh 
  3. apk add curl 

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

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

  1. Dockerfile  
  2. FROM maven:3.8.1-openjdk-17-slim AS MAVEN_BUILD 
  3. WORKDIR /build/ 
  4. COPY pom.xml /build/ 
  5. COPY src /build/src/ 
  6. RUN mvn package 
  7.  
  8. FROM openjdk:17-alpine 
  9.  
  10. RUN rm -f /sbin/apk && \ 
  11. rm -rf /etc/apk && \ 
  12. rm -rf /lib/apk && \ 
  13. rm -rf /usr/share/apk && \ 
  14. rm -rf rm -rf /var/lib/apk 
  15.  
  16. COPY --from=MAVEN_BUILD /build/target/springbootmaven.jar /springbootmaven.jar 
  17. EXPOSE 8080 
  18. CMD java -jar /springbootmaven.jar 

在此重建并重新运行:

  1. Shell  
  2. First let's stop the previously running container 
  3. docker stop springboot  
  4. Next let's re-build and re-run 
  5. docker build . -f Dockerfile.asr -t springbootmaven  
  6. docker run --name springboot -p 8080:8080 springbootmaven  
  7. docker run --name springboot -d -p 8080:8080 springbootmaven  
  8. curl http://localhost:8080 

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

  1. Shell  
  2. docker exec -it springboot /bin/sh  
  3. apk add curl 

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

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

另一方面,还可以查看由谷歌公司创建的distroless容器,这也是非常值得推荐的。

(2)切换用户场景

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

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

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

  1. Shell  
  2. docker exec -it springboot /bin/sh  
  3. whoami  
  4. ping rohitsalecha.com  

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

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

  1. Dockerfile  
  2. FROM maven:3.8.1-openjdk-17-slim AS MAVEN_BUILD 
  3. WORKDIR /build/ 
  4. COPY pom.xml /build/ 
  5. COPY src /build/src/ 
  6. RUN mvn package 
  7.  
  8. FROM openjdk:17-alpine 
  9.  
  10. # Removing apk package manager 
  11. RUN rm -f /sbin/apk && \ 
  12. rm -rf /etc/apk && \ 
  13. rm -rf /lib/apk && \ 
  14. rm -rf /usr/share/apk && \ 
  15. rm -rf rm -rf /var/lib/apk 
  16.  
  17. # Adding a user and group called "boot" 
  18. RUN addgroup boot -g 1337 && \  
  19. adduser -D -h /home/boot -u 1337 -s /bin/ash boot -G boot 
  20.  
  21. # Changing the context that shall run the below commands with User "boot" instead of root 
  22. USER boot 
  23. WORKDIR /home/boot 
  24.  
  25. By default even in a non-root context, Docker copies the file as root. Hence its best practice to chown 
  26. # the files being copied as the user. https://stackoverflow.com/a/44766666/1679541 
  27. COPY --chown=boot:boot --from=MAVEN_BUILD /build/target/springbootmaven.jar /home/boot/springbootmaven.jar 
  28. EXPOSE 8080 
  29. CMD java -jar /home/boot/springbootmaven.jar 

重建并重新运行:

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

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

  1. Shell  
  2. docker exec -it springboot /bin/sh  
  3. whoami  
  4. ping rohitsalecha.com 

运行时的安全性

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

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

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

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

  1. YAML  
  2. Create Namespace 
  3. apiVersion: v1 
  4. kind: Namespace 
  5. metadata: 
  6.  name: boot 
  7.  
  8. --- 
  9. Create SpringBoot Deployment 
  10. apiVersion: apps/v1 
  11. kind: Deployment 
  12. metadata: 
  13. labels: 
  14. app: springbootmaven 
  15. name: springbootmaven 
  16. namespace: boot 
  17. spec: 
  18. replicas: 1 
  19. selector: 
  20. matchLabels: 
  21. app: springbootmaven 
  22. template: 
  23. metadata: 
  24. labels: 
  25.  app: springbootmaven 
  26. spec: 
  27. containers: 
  28. - image: salecharohit/springbootmaven 
  29. name: springbootmaven 
  30.  ports: 
  31. - containerPort: 8080 
  32.  
  33. --- 
  34. Create Service for SpringBoot Deployment 
  35. apiVersion: v1 
  36. kind: Service 
  37. metadata: 
  38.  labels: 
  39.  app: springbootmaven 
  40. name: springbootmaven 
  41. namespace: boot 
  42. spec: 
  43. ports: 
  44. name"http" 
  45. port: 8080 
  46. targetPort: 8080 
  47. selector: 
  48. app: springbootmaven 

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

  1. Shell  
  2. kubectl apply -f kubernetes-basic.yaml  
  3. kubectl get deploy -n boot 
  4. # Run a temporary container that will only curl our bootservice 
  5. kubectl run -it testpod --image=radial/busyboxplus:curl --restart=Never --rm -- curl http://springbootmaven.boot.svc.cluster.local:8080  
  6.  
  7. ​Expected Output:  
  8. 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应用程序在实践中查看这一点。

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

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

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

  • 不需要访问API-Server的Pod

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

  1. YAML  
  2. 1 serviceAccountName: "" 
  3. 2 automountServiceAccountToken: false 

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

  1. YAML  
  2. apiVersion: apps/v1 
  3. kind: Deployment 
  4. metadata: 
  5. labels: 
  6. app: springbootmaven 
  7.  name: springbootmaven 
  8. namespace: boot 
  9. spec: 
  10. eplicas: 1 
  11. selector: 
  12. matchLabels: 
  13. app: springbootmaven 
  14. template: 
  15. metadata: 
  16. labels: 
  17. app: springbootmaven 
  18. spec: 
  19. containers: 
  20. image: salecharohit/springbootmaven 
  21. name: springbootmaven 
  22. ports: 
  23. - containerPort: 8080 
  24. serviceAccountName: "" 
  25. automountServiceAccountToken: false   

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

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

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

  • 需要访问API-Server的Pod

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

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

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

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

  1. YAML  
  2. Create Namespace 
  3. apiVersion: v1 
  4. kind: Namespace 
  5. metadata: 
  6. name: boot 
  7.  
  8. --- 
  9. apiVersion: v1 
  10. kind: ServiceAccount 
  11. metadata: 
  12. name: bootserviceaccount 
  13. namespace: boot 
  14.  
  15. --- 
  16. kind: Role 
  17. apiVersion: rbac.authorization.k8s.io/v1 
  18. metadata: 
  19. name: bootservicerole 
  20. namespace: boot 
  21. rules: 
  22. - apiGroups: [""
  23. resources: ["pods"
  24. verbs: ["get""list""watch"
  25.  
  26. --- 
  27. kind: RoleBinding 
  28. apiVersion: rbac.authorization.k8s.io/v1 
  29. metadata: 
  30. name: bootservicerolebinding 
  31. namespace: boot 
  32. subjects: 
  33. - kind: ServiceAccount 
  34. name: bootserviceaccount 
  35. namespace: boot 
  36. roleRef: 
  37. kind: Role 
  38. name: bootservicerole 
  39. apiGroup: rbac.authorization.k8s.io 
  40.  
  41. --- 
  42. Create SpringBoot Deployment 
  43. apiVersion: apps/v1 
  44. kind: Deployment 
  45. metadata: 
  46. labels: 
  47. app: springbootmaven 
  48. name: springbootmaven 
  49. namespace: boot 
  50. spec: 
  51. replicas: 1 
  52. selector: 
  53. matchLabels: 
  54. app: springbootmaven 
  55. template: 
  56. metadata: 
  57. labels: 
  58. app: springbootmaven 
  59. spec: 
  60. containers: 
  61. - image: salecharohit/springbootmaven 
  62. name: springbootmaven 
  63. ports: 
  64.  - containerPort: 8080 
  65. serviceAccountName: bootserviceaccount 
  66.  
  67. --- 
  68. Create Service for SpringBoot Deployment 
  69. apiVersion: v1 
  70. kind: Service 
  71. metadata: 
  72. labels: 
  73. app: springbootmaven 
  74. name: springbootmaven 
  75. namespace: boot 
  76. spec: 
  77. ports: 
  78.  - name"http" 
  79. port: 8080 
  80. targetPort: 8080 
  81. selector: 
  82. app: springbootmaven 

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

  1. # Ensure our previous deploy is deleted.  
  2. kubectl delete ns boot  
  3. kubectl apply -f kubernetes-withsa.yaml  
  4. 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中运行的所有容器
  1. YAML  
  2.  --- 
  3. securityContext: 
  4. fsGroup: 1337 
  5. runAsNonRoot: true 
  6. runAsUser: 1337 
  7. containers: 
  8. --- 
  • 在容器级别运行
  1. YAML  
  2. --- 
  3. securityContext: 
  4. allowPrivilegeEscalation: false 
  5. privileged: false 
  6. runAsUser: 1337 
  7. capabilities: 
  8. drop: ["SETUID""SETGID"
  9. serviceAccountName: "" 
  10. automountServiceAccountToken: false 
  11. --- 

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

  1. YAML  
  2. Create Namespace 
  3. apiVersion: v1 
  4. kind: Namespace 
  5. metadata: 
  6. name: boot 
  7. --- 
  8. Create SpringBoot Deployment 
  9. apiVersion: apps/v1 
  10. kind: Deployment 
  11. metadata: 
  12. labels: 
  13. app: springbootmaven 
  14. name: springbootmaven 
  15. namespace: boot 
  16. spec: 
  17. replicas: 1 
  18. selector: 
  19. matchLabels: 
  20. app: springbootmaven 
  21. template: 
  22. metadata: 
  23. labels: 
  24. app: springbootmaven 
  25. spec: 
  26. securityContext: 
  27. fsGroup: 1337 
  28. runAsNonRoot: true 
  29. runAsUser: 1337 
  30.  containers: 
  31. - image: salecharohit/springbootmaven 
  32. name: springbootmaven 
  33. ports: 
  34. - containerPort: 8080 
  35. securityContext: 
  36. allowPrivilegeEscalation: false 
  37. privileged: false 
  38. runAsUser: 1337 
  39. capabilities: 
  40. drop: ["SETUID""SETGID"
  41. erviceAccountName: "" 
  42. automountServiceAccountToken: false 
  43. --- 
  44. Create Service for SpringBoot Deployment 
  45. apiVersion: v1 
  46. kind: Service 
  47. metadata: 
  48. labels: 
  49.  app: springbootmaven 
  50. name: springbootmaven 
  51. namespace: boot 
  52. spec: 
  53. ports: 
  54. name"http" 
  55. port: 8080 
  56. targetPort: 8080 
  57. selector: 
  58. app: springbootmaven 

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

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

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

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

(3)不可变文件系统

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

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

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

  1. YAML  
  2. --- 
  3. containers: 
  4. - image: salecharohit/springbootmaven 
  5. name: springbootmaven 
  6. ports: 
  7. - containerPort: 8080 
  8. securityContext: 
  9. readOnlyRootFilesystem: true 
  10. volumeMounts: 
  11. - mountPath: /tmp 
  12. name: tmp 
  13. volumes: 
  14. - emptyDir: {} 
  15. name: tmp 
  16.  --- 

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

  1. YAML  
  2. Create Namespace 
  3. apiVersion: v1 
  4. kind: Namespace 
  5. metadata: 
  6. name: boot 
  7. --- 
  8. Create SpringBoot Deployment 
  9. apiVersion: apps/v1 
  10. kind: Deployment 
  11.  metadata: 
  12. labels: 
  13. app: springbootmaven 
  14. name: springbootmaven 
  15. namespace: boot 
  16. spec: 
  17. replicas: 1 
  18. selector: 
  19. matchLabels: 
  20. app: springbootmaven 
  21. template: 
  22. metadata: 
  23. labels: 
  24. app: springbootmaven 
  25. spec: 
  26. securityContext: 
  27. fsGroup: 1337 
  28. runAsNonRoot: true 
  29. runAsUser: 1337 
  30. containers: 
  31. - image: salecharohit/springbootmaven 
  32. name: springbootmaven 
  33. ports: 
  34. - containerPort: 8080 
  35. securityContext: 
  36. allowPrivilegeEscalation: false 
  37. readOnlyRootFilesystem: true 
  38. privileged: false 
  39. runAsUser: 1337 
  40. capabilities: 
  41. drop: ["SETUID""SETGID"
  42. volumeMounts: 
  43. - mountPath: /tmp 
  44. name: tmp 
  45. serviceAccountName: "" 
  46. automountServiceAccountToken: false 
  47.  volumes: 
  48. - emptyDir: {} 
  49. name: tmp 
  50.  
  51. --- 
  52. Create Service for SpringBoot Deployment 
  53. apiVersion: v1 
  54. kind: Service 
  55. metadata: 
  56. labels: 
  57. app: springbootmaven 
  58. name: springbootmaven 
  59. namespace: boot 
  60. spec: 
  61. ports: 
  62. name"http" 
  63. port: 8080 
  64. targetPort: 8080 
  65. selector: 
  66. app: springbootmaven 

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

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

结论

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

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

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

原文标题:Bootstrap Security in Kubernetes Deployments,作者:Rohit Salecha

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

【编辑推荐】

  1. Java、Golang项目基于kubernetes集群CICD实践案例【Gogs工具链】
  2. 数仓开发沦为了取数工具人,该如何破局?
  3. 除了Prometheus,监控K8S还有六种开源工具可选
  4. 阿里云全新容器服务ACK Anywhere :拓展云边界至企业每个场景
  5. Kubernetes:用虚拟集群节省成本
【责任编辑:华轩 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

视频课程+更多

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微