确保 DigitalOcean Kubernetes 集群安全的建议步骤

作者选择了 开源精神疾病以作为 写给捐赠计划的一部分获得捐赠。

介绍

Kubernetes,开源集装箱管制平台,正在稳步成为自动化,扩展和管理高可用性集群的首选解决方案。

考虑到 Kubernetes 所涉及的移动部件和各种部署场景,保护 Kubernetes 有时可能很复杂. 因此,本文的目的是为 DigitalOcean Kubernetes (DOKS) 集群提供坚实的安全基础。 请注意,本教程涵盖了 Kubernetes 的基本安全措施,并且旨在作为一个起点而不是一个完整的指南。 有关其他步骤,请参阅 官方 Kubernetes 文档

您将使用 TLS/SSL 证书配置安全本地身份验证,以 Role-based access controls (RBAC)授予本地用户权限,以 服务帐户授予 Kubernetes 应用程序和部署权限,并使用ResourceQuotaLimitRange输入控制器设置资源限制。

前提条件

为了完成本教程,您将需要:

步骤1:启用远程用户身份验证

完成前提后,您最终会有一个Kubernetes超级用户通过预定义的DigitalOcean承载符号进行身份验证,但是,共享这些身份验证不是一个良好的安全实践,因为该帐户可能会对您的集群产生大规模和可能破坏性的变化。

在本节中,您将使用安全的 SSL/TLS 证书对来自本地客户端的远程 DOKS 集群进行新用户身份验证。这将是一个三步进程:首先,您将为每个用户创建 Certificate Signing Requests (CSR),然后通过 `kubectl' 直接在集群中批准这些证书。 最后,您将为每个用户创建一个 kubeconfig文件,其中包含相应的证书。 有关 Kubernetes 支持的其他身份验证方法的更多信息,请参阅 Kubernetes 身份验证文档

创建证书 签署新用户请求

在开始之前,请检查在前提条件下配置的本地机器的DOKS集群连接:

1kubectl cluster-info

根据您的配置,输出将类似于此:

1[secondary_label Output]
2Kubernetes master is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com
3CoreDNS is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
4
5To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

这意味着您已连接到 DOKS 集群。

接下来,为客户端的证书创建一个本地文件夹. 为本指南的目的, `~/certs 将用于存储所有证书:

1mkdir ~/certs

在本教程中,我们将授权一个名为 sammy的新用户访问集群. 请随意将此更改为您选择的用户。 使用 SSL 和 TLS 库 OpenSSL,使用以下命令为您的用户生成一个新的私钥:

1openssl genrsa -out ~/certs/sammy.key 4096

标志将使输出文件~/certs/sammy.key,并且4096将密钥设置为4096位。

现在,创建一个签名证书请求配置文件. 使用文本编辑器打开以下文件(对于本教程,我们将使用nano):

1nano ~/certs/sammy.csr.cnf

将下列内容添加到 sammy.csr.cnf 文件中,以在主题中指定所需的用户名为一般名称(CN),并将组称为组织(O):

 1[label ~/certs/sammy.csr.cnf]
 2[ req ]
 3default_bits = 2048
 4prompt = no
 5default_md = sha256
 6distinguished_name = dn
 7[ dn ]
 8CN = sammy
 9O = developers
10[ v3_ext ]
11authorityKeyIdentifier=keyid,issuer:always
12basicConstraints=CA:FALSE
13keyUsage=keyEncipherment,dataEncipherment
14extendedKeyUsage=serverAuth,clientAuth

证书签名请求配置文件包含所有必要的信息、用户身份和用户的正确使用参数. 最后一个参数 `extendedKeyUsage=serverAuth,clientAuth' 将允许用户在签名后使用证书来验证本地客户端的 DOKS 集群。

然后创建 sammy 证书签名请求:

1openssl req -config ~/certs/sammy.csr.cnf -new -key ~/certs/sammy.key -nodes -out ~/certs/sammy.csr

-config允许您指定 CSR 的配置文件,并发出信号,表示您正在为-key 指定的密钥创建新的 CSR。

您可以通过运行以下命令检查您的证书签名请求:

1openssl req -in ~/certs/sammy.csr -noout -text

在这里,您通过-in进入CSR,并使用-text将证书请求打印成文本。

输出将显示证书请求,其开始将是这样的:

1[secondary_label Output]
2Certificate Request:
3    Data:
4        Version: 1 (0x0)
5        Subject: CN = sammy, O = developers
6        Subject Public Key Info:
7            Public Key Algorithm: rsaEncryption
8                RSA Public-Key: (4096 bit)
9...

重复相同的过程,为任何额外的用户创建 CSRs. 一旦您已将所有证书签名请求保存到管理员的 ~/certs 文件夹中,请继续下一步来批准它们。

使用 Kubernetes API 管理证书签名请求

您可以通过使用kubectl命令行工具批准或拒绝发给 Kubernetes API 的 TLS 证书,这样您就可以确保所请求的访问对特定用户是合适的。

要将 CSR 发送到 DOKS 集群,请使用以下命令:

 1cat <<EOF | kubectl apply -f -
 2apiVersion: certificates.k8s.io/v1beta1
 3kind: CertificateSigningRequest
 4metadata:
 5  name: sammy-authentication
 6spec:
 7  groups:
 8  - system:authenticated
 9  request: $(cat ~/certs/sammy.csr | base64 | tr -d '\n')
10  usages:
11  - digital signature
12  - key encipherment
13  - server auth
14  - client auth
15EOF

使用 Bash here document,此命令使用cat传输证书请求到kubectl应用

让我们仔细看看证书请求:

  • name: sammy-authentication 创建了一个元数据标识符,在这种情况下称为 sammy-authentication.
  • request: $(cat ~/certs/sammy.csr Mediateca base64 Mediateca tr -d '\n')sammy.csr 证书签名请求发送到编码为 Base64 的集群中。

结果将看起来像这样:

1[secondary_label Output]
2certificatesigningrequest.certificates.k8s.io/sammy-authentication created

您可以使用命令检查证书签名请求状态:

1kubectl get csr

根据您的集群配置,输出将类似于此:

1[secondary_label Output]
2NAME AGE REQUESTOR CONDITION
3sammy-authentication 37s your_DO_email Pending

接下来,通过以下命令批准 CSR:

1kubectl certificate approve sammy-authentication

您将收到确认操作的消息:

1[secondary_label Output]
2certificatesigningrequest.certificates.k8s.io/sammy-authentication approved

<$>[注] **注:**作为管理员,您也可以通过使用命令「kubectl 证书拒绝 sammy-authentication」来拒绝 CSR。

现在 CSR 已获得批准,您可以通过运行下载到本地机器:

1kubectl get csr sammy-authentication -o jsonpath='{.status.certificate}' | base64 --decode > ~/certs/sammy.crt

此命令将 Base64 证书解码为kubectl,然后将其保存为~/certs/sammy.crt

有了 sammy 签名的证书,您现在可以创建用户的 kubeconfig文件。

构建远程用户 Kubeconfig

接下来,您将为 sammy用户创建一个特定的 kubeconfig文件,这将为您提供对用户访问您的集群的更多控制权。

构建新的 kubeconfig 的第一步是创建当前的 kubeconfig 文件的副本. 为本指南的目的,新的 kubeconfig 文件将被称为 config-sammy:

1cp ~/.kube/config ~/.kube/config-sammy

接下来,编辑新文件:

1nano ~/.kube/config-sammy

保持本文件的前八行,因为它们包含与群集连接的SSL/TLS所需信息,然后从用户参数开始,用以下突出的行代替文本,使文件看起来像以下:

 1[label config-sammy]
 2apiVersion: v1
 3clusters:
 4- cluster:
 5    certificate-authority-data: certificate_data
 6  name: do-nyc1-do-cluster
 7contexts:
 8- context:
 9    cluster: do-nyc1-do-cluster
10    user: sammy
11  name: do-nyc1-do-cluster
12current-context: do-nyc1-do-cluster
13kind: Config
14preferences: {}
15users:
16- name: sammy
17  user:
18    client-certificate: /home/your_local_user/certs/sammy.crt
19    client-key: /home/your_local_user/certs/sammy.key

<$>[注] **注:**对于客户端证书客户端密钥,使用到相应的证书位置的绝对路径。

保存和退出文件。

您可以使用kubectl cluster-info测试新的用户连接:

1kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy cluster-info

您将看到类似于此的错误:

1[secondary_label Output]
2To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
3Error from server (Forbidden): services is forbidden: User "sammy" cannot list resource "services" in API group "" in the namespace "kube-system"

此错误是预期的,因为用户 sammy 尚未授权列出任何资源在集群中。 授权用户将在下一个步骤中覆盖。

步骤 2 – 通过基于角色访问控制(RBAC)授权用户

一旦用户被身份验证,API 会使用 Kubernetes 内置的基于角色访问控制 (RBAC) 模型来确定其权限。RBAC 是基于分配给用户的角色来限制用户权限的有效方法。从安全角度来看,RBAC 允许设置精细的权限,以限制用户访问敏感数据或执行超级用户级命令。

在此步骤中,您将使用kubectl将预定义的角色 edit分配给用户 sammydefault命名空间中。

授予许可

在 Kubernetes 中,授予权限意味着将所需的角色分配给用户。

1kubectl create rolebinding sammy-edit-role --clusterrole=edit --user=sammy --namespace=default

这将产生类似于以下的产量:

1[secondary_label Output]
2rolebinding.rbac.authorization.k8s.io/sammy-edit-role created

让我们更详细地分析这个命令:

  • create rolebinding sammy-edit-role 创建了一个新的角色绑定,在这种情况下称为 sammy-edit-role.
  • --clusterrole=edit 将预定义的角色 edit 分配到全球范围内(集群角色)。
  • --user=sammy 指定用户将该角色绑定到
  • --namespace=default 授予用户在指定名空间内的角色权限,在这种情况下 default

接下来,通过在默认名称空间中列出 pods 来验证用户权限,如果没有出现错误,您可以确定 RBAC 授权是否按预期运行。

1kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy auth can-i get pods

你会得到以下的输出:

1[secondary_label Output]
2yes

现在您已将权限分配给 sammy,您现在可以在下一节中练习取消这些权限。

撤销许可证

Kubernetes 中的权限取消是通过删除用户角色约束。

对于本教程,请通过运行以下命令从用户 sammy中删除编辑角色:

1kubectl delete rolebinding sammy-edit-role

你会得到以下的输出:

1[secondary_label Output]
2rolebinding.rbac.authorization.k8s.io "sammy-edit-role" deleted

通过列出默认名称空间子列表来验证用户权限是否按预期被撤销:

1kubectl --kubeconfig=/home/localuser/.kube/config-sammy --namespace=default get pods

您将收到以下错误:

1[secondary_label Output]
2Error from server (Forbidden): pods is forbidden: User "sammy" cannot list resource "pods" in API group "" in the namespace "default"

这表明授权已经被撤销。

从安全角度来看,Kubernetes授权模型为群集管理员提供了灵活性,可以根据需要更改用户权限,此外,基于角色的访问控制不限于一个实体用户;您还可以授予和删除群集服务的权限,如下节所示。

有关 RBAC 授权以及如何创建自定义角色的更多信息,请阅读 官方文档

步骤 3 – 使用服务帐户管理应用程序权限

如前面所述,RBAC授权机制超越了人类用户的范围。非人类群集用户,如应用程序、服务和进程,可以通过Kubernetes称之为服务帐户的API服务器进行身份验证。当在名称空间中创建一个群集时,您可以让它使用默认服务帐户,或者您可以定义您选择的服务帐户。将个别SA分配给应用程序和进程的能力使管理员可以根据需要自由地授予或撤销权限。

为了演示服务帐户,本教程将使用 Nginx Web 服务器作为样本应用程序。

在为您的应用程序分配特定 SA 之前,您需要创建 SA. 在默认名称空间中创建一个名为nginx-sa的新服务帐户:

1kubectl create sa nginx-sa

你会得到:

1[secondary_label Output]
2serviceaccount/nginx-sa created

通过运行以下操作来验证服务帐户是否已创建:

1kubectl get sa

这将为您提供您的服务帐户列表:

1[secondary_label Output]
2NAME SECRETS AGE
3default 1 22h
4nginx-sa 1 80s

现在,您将为nginx-sa服务帐户分配一个角色,为此示例,向nginx-sa授予与 sammy用户相同的权限:

1kubectl create rolebinding nginx-sa-edit \
2 --clusterrole=edit \
3 --serviceaccount=default:nginx-sa \
4 --namespace=default

运行这将产生如下:

1[secondary_label Output]
2rolebinding.rbac.authorization.k8s.io/nginx-sa-edit created

此命令使用与用户 sammy相同的格式,除了--serviceaccount=default:nginx-sa旗帜之外,您在默认名称空间中分配nginx-sa服务帐户。

检查角色绑定是否成功使用此命令:

1kubectl get rolebinding

这将产生以下产出:

1[secondary_label Output]
2NAME AGE
3nginx-sa-edit 23s

确认服务帐户的角色约束性已成功配置后,您可以将服务帐户分配给应用程序. 将特定服务帐户分配给应用程序将允许您实时管理其访问权限,从而增强集群安全性。

对于本教程的目的,一个nginxpod将作为样本应用程序。创建新的pod,并用以下命令指定nginx-sa服务帐户:

1kubectl run nginx --image=nginx --port 80 --serviceaccount="nginx-sa"

命令的第一部分会创建一个新的pod,在端口 :80 上运行nginx Web 服务器,而最后一部分--serviceaccount="nginx-sa 表示该pod 应该使用nginx-sa服务帐户而不是默认` SA。

这将为您提供类似于以下的输出:

1[secondary_label Output]
2deployment.apps/nginx created

通过使用kubectl 描述来验证新应用正在使用服务帐户:

1kubectl describe deployment nginx

这将产生部署参数的长描述. 在Pod 模板部分中,您将看到类似于此的输出:

1[secondary_label Output]
2...
3Pod Template:
4 Labels: run=nginx
5 Service Account: nginx-sa
6...

在本节中,您在默认名称空间中创建了nginx-sa服务帐户,并将其分配给nginx网页服务器。现在您可以通过根据需要更改其角色来实时控制nginx权限。

总之,将角色分配给您的应用程序/部署的想法是精准调节权限. 在现实世界的生产环境中,您可能有几个部署需要不同的权限,从只读到完整的管理权限。

接下来,您将设置入口控制器来控制资源并防范资源饥饿攻击。

步骤4:设置入学控制器

Kubernetes admission controllers是可选的插件,这些插件被编译成kube-apiserver二进制,以扩展安全选项。

虽然验证或授权检查的结果是允许或拒绝请求的布尔语,但接入控制器可以更为多样化,接入控制器可以以与身份验证相同的方式验证请求,但也可以 mutate 或更改请求并在接入之前修改对象。

在此步骤中,您将使用ResourceQuotaLimitRange输入控制器来保护您的群集,通过突变可能导致资源饥饿或拒绝服务攻击的请求(https://owasp.org/www-community/attacks/Denial_of_Service)。ResourceQuota输入控制器允许管理员限制计算资源,存储资源和名称空间中的任何对象数量,而LimitRange输入控制器将限制容器使用的资源数量。

为了展示ResourceQuota的运作方式,您将在默认名称空间中实施一些限制,首先创建一个新的ResourceQuota对象文件:

1nano resource-quota-default.yaml

添加以下对象定义以在默认名称空间中设置资源消耗的限制。

 1[label resource-quota-default.yaml]
 2apiVersion: v1
 3kind: ResourceQuota
 4metadata:
 5  name: resource-quota-default
 6spec:
 7  hard:
 8    pods: "2"
 9    requests.cpu: "500m"
10    requests.memory: 1Gi
11    limits.cpu: "1000m"
12    limits.memory: 2Gi
13    configmaps: "5"
14    persistentvolumeclaims: "2"
15    replicationcontrollers: "10"
16    secrets: "3"
17    services: "4"
18    services.loadbalancers: "2"

该定义使用关键字来设置硬约束,如podsconfigmapsPersistentVolumeClaimsReplicationControllerssecretsservicesload balancers的最大数量。

  • requests.cpu,它将请求的最大 CPU 值设置为 milliCPU,或一个 CPU 内核的千分之一
  • requests.memory,它将请求的最大内存值设置为字节
  • limits.cpu,它将 milliCPU 中的最大 CPU 值设置为限值
  • limits.memory,它将最大内存值设置为字节

保存和退出文件。

现在,在运行以下命令的名称空间中创建对象:

1kubectl create -f resource-quota-default.yaml --namespace=default

这将产生如下:

1[secondary_label Output]
2resourcequota/resource-quota-default created

请注意,您正在使用-f旗,向Kubernetes表示ResourceQuota文件的位置,以及--namespace旗,以指定将更新哪个名称空间。

一旦对象被创建,你的ResourceQuota将是活跃的. 你可以检查默认名称空间比率与描述比率:

1kubectl describe quota --namespace=default

输出将类似于此,您在resource-quota-default.yaml文件中设置的硬限制:

 1[secondary_label Output]
 2Name:                   resource-quota-default
 3Namespace:              default
 4Resource Used Hard
 5--------                ----  ----
 6configmaps 0 5
 7limits.cpu 0 1
 8limits.memory 0 2Gi
 9persistentvolumeclaims 0 2
10pods 1 2
11replicationcontrollers 0 10
12requests.cpu 0 500m
13requests.memory 0 1Gi
14secrets 2 3
15services 1 4
16services.loadbalancers 0 2

「ResourceQuotas」以绝对单位表示,因此添加额外的节点不会自动增加这里定义的值. 如果添加更多节点,您将需要手动编辑此处的值来比例资源。

如果您需要修改某个特定的ResourceQuota,请更新相应的.yaml文件,并使用以下命令应用更改:

1kubectl apply -f resource-quota-default.yaml --namespace=default

有关资源配额入学控制员的更多信息,请参阅 官方文件

现在,当您的ResourceQuota已设置,您将继续配置LimitRange入口控制器。类似于ResourceQuota如何对名称空间施加限制,LimitRange也执行通过验证和突变容器声明的限制。

与以前类似的方式,开始创建对象文件:

1nano limit-range-default.yaml

现在,您可以使用LimitRange对象来根据需要限制资源的使用。

 1[label limit-ranges-default.yaml]
 2apiVersion: v1
 3kind: LimitRange
 4metadata:
 5  name: limit-range-default
 6spec:
 7  limits:
 8  - max:
 9      cpu: "400m"
10      memory: "1Gi"
11    min:
12      cpu: "100m"
13      memory: "100Mi"
14    default:
15      cpu: "250m"
16      memory: "800Mi"
17    defaultRequest:
18      cpu: "150m"
19      memory: "256Mi"
20    type: Container

在 limit-range-default.yaml 中使用的样本值限制容器内存最多为 1Gi,并限制CPU使用量最多为 400m,这相当于 Kubernetes 指数相当于 400 milliCPU,这意味着容器仅使用近一半的核心。

接下来,使用以下命令将对象部署到API服务器:

1kubectl create -f limit-range-default.yaml --namespace=default

这将产生以下产出:

1[secondary_label Output]
2limitrange/limit-range-default created

现在您可以通过以下命令检查新的限制:

1kubectl describe limits --namespace=default

你的输出将看起来像这样:

1[secondary_label Output]
2Name:       limit-range-default
3Namespace:  default
4Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
5----        --------  ---    ---   ---------------  -------------  -----------------------
6Container cpu 100m 400m 150m 250m           -
7Container memory 100Mi 1Gi 256Mi 800Mi          -

要看到LimitRanger在行动中,使用以下命令部署标准的nginx容器:

1kubectl run nginx --image=nginx --port=80 --restart=Never

这将产生以下产出:

1[secondary_label Output]
2pod/nginx created

检查输入控制器如何通过运行以下命令来突变容器:

1kubectl get pod nginx -o yaml

查看容器规格部分以查找LimitRange输入控制器中规定的资源限制:

 1[secondary_label Output]
 2...
 3spec:
 4  containers:
 5  - image: nginx
 6  imagePullPolicy: IfNotPresent
 7  name: nginx
 8  ports:
 9  - containerPort: 80
10    protocol: TCP
11  resources:
12    limits:
13      cpu: 250m
14      memory: 800Mi
15    requests:
16      cpu: 150m
17      memory: 256Mi
18...

这就像在容器规格中手动声明资源请求一样。

在此步骤中,您使用了ResourceQuotaLimitRange输入控制器来防止对群集资源的恶意攻击,有关LimitRange输入控制器的更多信息,请参阅 官方文档

结论

在本指南中,您配置了一种基本的Kubernetes安全模板,它建立了用户身份验证和授权、应用程序权限和集群资源保护。 结合本文中涵盖的所有建议,您将为生产Kubernetes集群部署奠定坚实的基础。

如果您想了解更多关于Kubernetes的信息,请访问我们的Kubernetes资源页面(https://www.digitalocean.com/resources/kubernetes/),或遵循我们的Kubernetes for Full-Stack Developers自导课程(https://www.digitalocean.com/community/curriculums/kubernetes-for-full-stack-developers)。

Published At
Categories with 技术
comments powered by Disqus