有效且高效管理“Kubernetes Secrets”的方法

首先
现在,Kubernetes已经成为容器编排的标准。由于许多企业逐渐采用容器优先的开发结构,大部分现有的工作负载都在公共云或私有数据中心的虚拟机上运行。因此,许多企业面临从以前的方式迁移到Kubernetes并同时面对其后续困难的挑战。
Kubernetes的转换会对包括监控、日志记录、CI/CD和最重要的安全性在内的整个DevOps流程产生影响,安全性应在集群级别和应用程序级别都得到处理。
在这篇文章中,我想讲解一下如何在Kubernetes中有效地管理应用程序的机密信息。
在Kubernetes中,机密信息如API集成令牌、OAuth令牌、数据库密码等由Secret对象管理。这些Secret可以作为挂载卷的形式让Pod访问。
如果在每个环境中运行不同的Kubernetes集群(强烈建议这样做),建议将环境特定的所有机密信息保存在一个地方。然后,确保有一个能够智能识别部署Pod的环境,并相应获取机密信息的机密管理工具。关于这一点,在本帖的后半部分将有详细说明。
开始使用Kubernetes Secrets
使用Kubectl创建秘密对象,并将其作为卷挂载。
让我们创建一个应用程序使用的令牌密码,以便在第三方服务中进行身份验证。
可以使用文字值或文件来创建Secret。 在这种情况下,将秘密信息放在名为access.txt的文件中。
$cat access.txt
APP_AUTH_TOKEN=WEj4VmNF755uc9vZdz98zvPXB6DkHp
$ kubectl create secret generic auth-token --from-file=./access.txt
secret "auth-token" created
$kubectl get secrets
NAME TYPE DATA AGE
auth-token Opaque 1 14s
default-token-k7vmv kubernetes.io/service-account-token 3 17m
$ kubectl describe secret auth-token
Name: auth-token
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
access.txt: 46 bytes
让我们将这个秘密作为一个已挂载的卷在Pod中使用。
首先,创建Demo Pod并应用清单。
$ cat pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- name: demo-pod
image: ubuntu
command: ["/bin/bash", "-ec", "while :; do echo '.'; sleep 5 ; done"]
volumeMounts:
- name: myvolume
mountPath: "/tmp"
readOnly: true
volumes:
- name: myvolume
secret:
secretName: auth-token
$ kubectl apply -f pod.yaml
pod "demo-pod" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
demo-pod 1/1 Running 0 1m
当在此Pod内执行时,应该能够找到已挂载到/tmp目录下的密钥。
$ kubectl exec -it demo-pod /bin/bash
root@demo-pod:/# ls /tmp
access.txt
root@demo-pod:/# cat /tmp/access.txt
APP_AUTH_TOKEN=WEj4VmNF755uc9vZdz98zvPXB6DkHp
root@demo-pod:/#
使用这种方法,您可以安全地挂载包含机密数据的配置文件,并且之后可以从已挂载的目录中的应用程序中读取它们。然而,有时候您可能也希望将机密数据作为应用程序的环境变量来使用。让我们在下一个部分尝试一下这样做。
将”2.2Secrets”作为Pod环境变量可用。
为了能够将密钥作为环境变量使用,我们需要创建和应用一个密钥对象清单。首先,将数据进行编码,然后将编码后的文本作为明文写入密钥文件中。
$ echo WEj4VmNF755uc9vZdz98zvPXB6DkHp | base64
V0VqNFZtTkY3NTV1Yzl2WmR6OTh6dlBYQjZEa0hwCg==
$ cat auth_secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
auth_token: V0VqNFZtTkY3NTV1Yzl2WmR6OTh6dlBYQjZEa0hwCg==
$ kubectl apply -f auth_secret.yaml
secret "mysecret" created
$ kubectl describe secret mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations:
Type: Opaque
Data
====
auth_token: 31 bytes
若要将Secret作为环境变量插入,需要使用存储Secret的密钥来访问Secret,并将其映射到Pod的环境变量中。以下示例中,我们将在另一个示范Pod demo-pod-2 中执行此操作。
$ cat pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod-2
spec:
containers:
- name: demo-pod-2
image: ubuntu
command: ["/bin/bash", "-ec", "while :; do echo '.'; sleep 5 ; done"]
env:
- name: APP_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: mysecret
key: auth_token
$ kubectl apply -f pod-2.yaml
pod "demo-pod-2" created
在这里,如果在demo-pod-2上执行,您可以确认APP_AUTH_TOKEN环境变量的值,并显示解码后的Secret值。
$ kubectl exec -it demo-pod-2 /bin/bash
root@demo-pod-2:/# echo $APP_AUTH_TOKEN
WEj4VmNF755uc9vZdz98zvPXB6DkHp
root@demo-pod-2:/#
继续阅读
继续阅读更多
最近,Kubernetes宣布了一个新功能,用于对保存的密码进行加密,我强烈推荐你去阅读一下。尽管这个功能非常新,但也值得尝试一下。
3. Kubernetes秘密管理的高级功能
在之前,我们学习了Kubernetes Secrets的工作原理以及如何在Pod中使用secret。然而,如果我们运行多个Kubernetes集群(开发/灰度/生产),我们就需要一个集中式的secret存储和安全机制来将所需的secret导入到Pod环境中。
我将介绍两种解决方案。
-
- 在基于Kubernetes的身份验证中使用Vault
- 通过将Chamber集成到Dockerfile中,从AWS参数存储加载Secret(适用于AWS上的Kubernetes集群)
3.1 使用Kubernetes作为基础认证的方式来使用Vault
Vault基于的认证工作流程可以总结如下。
-
- Pods are deployed in specific namespaces and associated with specific service accounts.
-
- The service accounts associated with namespaces are linked to the Kubernetes authentication role for the Vault backend.
-
- Pods authenticate with Vault using the token of the service account to obtain the VAULT_TOKEN.
Using the VAULT_TOKEN and VAULT_ADDRESS, secrets can be securely retrieved from Vault.
另外,在将这些Secrets作为环境变量注入到Pod中,有几个选项可供选择。稍后将对此进行解释。
3.2 Vault是什么?
Vault是一种轻量级工具,用于有效地保存和管理秘密。它完全加密,并支持安全的Kubernetes认证。Vault通常由Consul作为存储引擎进行支持。因此,它非常可靠且具有高度的容错能力,能够在节点故障时恢复。
3.3 设置Vault
为了为Kubernetes应用程序配置Vault,有几种方法可供选择。
-
- 在可从整个环境访问的Kubernetes集群中配置Vault。
- 使用托管版本的Vault。
在这篇博文中,我们将不涉及如何在Kubernetes上设置Vault,但我将列出可用的资源。
-
- 以下是用中文原生方式释义的选项:
https://www.hashicorp.com/blog/announcing-the-vault-helm-chart
– 哈希公司发布了Vault Helm Chart。
https://github.com/hashicorp/consul-helm
– 哈希公司的Consul Helm仓库。
一旦设置好Vault,就可以连接到Kubernetes集群。正如前面所述,我们的目标是使得Pod能够有效地通过Vault进行认证,并使应用程序能够获取Secret。
使用Vault认证Kubernetes Pod
首先,创建一个具有以下权限的Kubernetes服务帐户。
$ cat vault_sa.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: default
$ kubectl apply -f vault_sa.yaml
clusterrolebinding.rbac.authorization.k8s.io "role-tokenreview-binding" created
接下来,我们会获取变量并在Vault后台启用Kubernetes认证。
Kubernetes主机:Vault可连接的地址。
k8s_host="$(kubectl config view --minify | grep server | cut -f 2- -d ":" | tr -d " ")"
集群认证数据: 用于验证连接的证书。
k8s_cacert="$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode)"
用户令牌 — Vault 认证服务账号扮演了审阅者的角色。Vault 使用该服务账号与集群进行交互。
secret_name="$(kubectl get serviceaccount vault-auth -o go-template='{{ (index .secrets 0).name }}')"
account_token="$(kubectl get secret ${secret_name} -o go-template='{{ .data.token }}' | base64 --decode)"
当获取到所有这些值时,您可以在Vault后端中启用Kubernetes认证。
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt=${account_token} \
kubernetes_host=${k8s_host} \
kubernetes_ca_cert=${k8s_cacert}
接下来,需要确保新启动的Pod可以在Vault服务器上进行身份验证。为此,需要将命名空间绑定到Vault角色,并稍后使用该命名空间的服务账户JWT进行认证。
vault write auth/kubernetes/role/demo bound_service_account_names=vault-auth bound_service_account_namespaces=default policies=demo-policy ttl=1h
我在这里来试一试!
我将创建一个演示用的Pod,并试图在Vault中进行认证。
kubectl run -it --rm --image=ubuntu --serviceaccount=vault-auth test -- /bin/bash
root$ apt-get update -y && apt-get install vim curl jq mysql-client -y
#Let's get the service account JWT token
root$ JWT="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
#Now we can use this to get the vault token
root$ VAULT_TOKEN="$(curl --request POST --data '{"jwt": "'"$JWT"'", "role": "demo"}' -s -k https://${VAULT_ADDRESS}/v1/auth/kubernetes/login | jq -r '.auth.client_token')"
通过使用VAULT_TOKEN,在Vault中进行身份验证并获取秘密。但是,这只是整个过程的一半。我们的目标是将Vault Secret配置到环境变量中,以供应用程序使用。
如果你的应用程序中有逻辑使用VAULT_TOKEN和VAULT_ADDRESS环境变量直接从Vault中读取数据的话,你可以完全跳过这部分。否则,执行以下操作。
-
- 将vaultenv与Docker容器集成
- 在入口点脚本的开头使用vaultenv来配置环境
4. 将密钥保存在AWS参数存储中。
4.1 AWS参数存储是什么?
这是一个由AWS提供的非常可扩展且安全的服务,用于存储Secrets。您可以使用AWS CLI来读写Secrets。如果要从EC2实例或ECS任务中进行访问,您需要配置适当的IAM角色。
4.2 什么是「チャンバー(Chamber)」?
Chamber是一个能够在AWS Parameter Store中读写秘钥的命令行实用工具。它支持许多其他命令,以便添加Secrets到执行环境或以各种格式导出秘钥。
开始这个,需要以下信息。
-
- AWS_DEFAULT_REGION: 亚马逊云服务默认区域
-
- AWS_SECRET_KEY_ID: 亚马逊云服务密钥ID
- AWS_SECRET_ACCESS_KEY: 亚马逊云服务访问密钥
4.3 Chamber进行本地安装
如果你正在使用Mac,你可以使用以下命令在本地安装Chamber。
brew update
brew install chamber
请确认笔记本电脑上已经设置了AWS命令行工具。
使用以下内容将Secret写入AWS参数存储。
chamber write <service> <key> <value>
使用以下方式,从AWS参数存储中读取秘钥。
chamber read <service> <key>
4.4 Chamber与Kubernetes应用程序的集成
要将Chamber与Kubernetes应用程序整合,需要进行一些较小的更改。
-
- 应用程序的Dockerfile
-
- 脚本的入口点
- 部署清单文件
在Dockerfile的顶部,添加以下内容。
FROM golang:1.10.4 AS build
RUN CGO_ENABLED=0 GOOS=linux go get -v github.com/segmentio/chamber
FROM <existing base image>
COPY --from=build /go/bin/chamber /chamber
…
通过这样做,Chamber二进制文件将被构建并集成到容器中。
在入口点脚本中,进行以下更改。
在主要入口逻辑启动之前,添加以下语句来设置环境变量。
在主要入口逻辑开始之前,添加下列语句设置环境变量。
eval "$(chamber env $SERVICE)"
在部署清单中需要设置以下环境变量。
-
- AWS_DEFAULT_REGION: 这对应于访问凭证的默认区域。
SERVICE: 这对应于想要使用Chamber从参数存储中读取Secrets的服务。
Chamber需要访问AWS_SECRET_KEY_ID和AWS_SECRET_ACCESS_KEY,但不建议在Pod清单文件中进行这些配置。强烈建议使用基于IAM角色的访问权限方法,在AWS Parameter Store中进行认证。为此,请确保为分配给Kubernetes工作节点的IAM角色启用了ssm:GetParameters操作。
当所有这些配置都完成后,Pod中的容器将会读取SERVICE环境变量中的秘密信息。然后,使用AWS参数存储进行身份认证,并将容器所需的所有秘密信息导入容器的环境中。
5. 总结
如果Kubernetes的密钥管理得当,可以显著简化部署过程。可以选择将它们插入应用程序的执行环境中,或使用自定义构建逻辑进行实时读取。
如果在AWS云上部署Kubernetes集群,我们强烈推荐使用Chamber和AWS参数存储(AWS Parameter Store)的整合。这是最简单且非常安全的开始方式。
此外,您还可以借助Kiam的帮助,通过使用细粒度的IAM访问来管理对参数存储库的访问。这样一来,只有集群内特定的Pod可以从AWS参数存储库获取和使用Secrets。然而,这又是另外一个讨论话题,以后再说吧。
那么,我们下一篇文章再见!