1 前言
默认 Secrets 对象的值是 base64 编码的内容,这个可以反编码得到原文的,不能起到加密重要密文的作用。
解决方法是使用开源的 Sealed Secrets
2 认识 Sealed Secrets
github 地址: https://github.com/bitnami-labs/sealed-secrets
有两部分总成:
- 集群里的: controller / operator
- 客户端程序: kubeseal
kubeseal 程序使用非对称加密来加密,只有控制器才能解密。
这些加密的秘密被编码在SealedSecret资源中,您可以将其视为创建秘密的配方。
它的 YAML 资源文件大概是这样的:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: mysecret
namespace: mynamespace
spec:
encryptedData:
foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....
经过 kubernetes 已安装的 controller 解密后相当于下面的内容:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: mynamespace
data:
foo: YmFy # <- base64 编码的 "bar"
当这个 SealedSecret 的 secret 被创建的几秒钟之后,它会出现在 kubernetes 中;随后你可以像使用 kubernetes 原生的 secret 一样使用它。例如,从 Pod 引用它。
部署
获取最新的版本 https://github.com/bitnami-labs/sealed-secrets/releases
选择自己喜欢的部署方式即可。
这里选择手动非工具部署
需要下载
- controller.yml 集群端 控制器
- kubeseal 客户端程序
controller.yml
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations: {}
labels:
name: secrets-unsealer
name: secrets-unsealer
rules:
- apiGroups:
- bitnami.com
resources:
- sealedsecrets
verbs:
- get
- list
- watch
- apiGroups:
- bitnami.com
resources:
- sealedsecrets/status
verbs:
- update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- create
- update
- delete
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secrets-unsealer
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
rules:
- apiGroups:
- ""
resourceNames:
- sealed-secrets-controller
resources:
- services
verbs:
- get
- apiGroups:
- ""
resourceNames:
- 'http:sealed-secrets-controller:'
- http:sealed-secrets-controller:http
- sealed-secrets-controller
resources:
- services/proxy
verbs:
- create
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-service-proxier
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-key-admin
name: sealed-secrets-key-admin
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-key-admin
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
ports:
- port: 8080
targetPort: 8080
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller-metrics
name: sealed-secrets-controller-metrics
namespace: kube-system
spec:
ports:
- port: 8081
targetPort: 8081
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
minReadySeconds: 30
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
name: sealed-secrets-controller
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
spec:
containers:
- args: []
command:
- controller
env: []
image: docker.io/bitnami/sealed-secrets-controller:0.26.1
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /healthz
port: http
name: sealed-secrets-controller
ports:
- containerPort: 8080
name: http
- containerPort: 8081
name: metrics
readinessProbe:
httpGet:
path: /healthz
port: http
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
stdin: false
tty: false
volumeMounts:
- mountPath: /tmp
name: tmp
imagePullSecrets: []
securityContext:
fsGroup: 65534
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
serviceAccountName: sealed-secrets-controller
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: sealedsecrets.bitnami.com
spec:
group: bitnami.com
names:
kind: SealedSecret
listKind: SealedSecretList
plural: sealedsecrets
singular: sealedsecret
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: SealedSecret is the K8s representation of a "sealed Secret" -
a regular k8s Secret that has been sealed (encrypted) using the controller's
key.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: SealedSecretSpec is the specification of a SealedSecret
properties:
data:
description: Data is deprecated and will be removed eventually. Use
per-value EncryptedData instead.
format: byte
type: string
encryptedData:
additionalProperties:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
template:
description: Template defines the structure of the Secret that will
be created from this sealed secret.
properties:
data:
additionalProperties:
type: string
description: Keys that should be templated using decrypted data
nullable: true
type: object
immutable:
description: Immutable, if set to true, ensures that data stored
in the Secret cannot be updated (only object metadata can be
modified). If not set to true, the field can be modified at
any time. Defaulted to nil.
type: boolean
metadata:
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
nullable: true
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
type:
description: Used to facilitate programmatic handling of secret
data.
type: string
type: object
required:
- encryptedData
type: object
status:
description: SealedSecretStatus is the most recently observed status of
the SealedSecret.
properties:
conditions:
description: Represents the latest available observations of a sealed
secret's current state.
items:
description: SealedSecretCondition describes the state of a sealed
secret at a certain point.
properties:
lastTransitionTime:
description: Last time the condition transitioned from one status
to another.
format: date-time
type: string
lastUpdateTime:
description: The last time this condition was updated.
format: date-time
type: string
message:
description: A human readable message indicating details about
the transition.
type: string
reason:
description: The reason for the condition's last transition.
type: string
status:
description: 'Status of the condition for a sealed secret. Valid
values for "Synced": "True", "False", or "Unknown".'
type: string
type:
description: 'Type of condition for a sealed secret. Valid value:
"Synced"'
type: string
required:
- status
- type
type: object
type: array
observedGeneration:
description: ObservedGeneration reflects the generation most recently
observed by the sealed-secrets controller.
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
基本使用
以某种方式创建一个json/yaml编码的Secret:
echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o yaml >mysecret.yml
内容如下:
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
creationTimestamp: null
name: mysecret
生成加密的 secret 资源文件
kubeseal -f mysecret.yml -w mysealedsecret.yml
数据被加密后的资源文件如下:
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: mysecret
namespace: default
spec:
encryptedData:
foo: AgA4KaUIHoNRNv8ZN1H1+dZfRyHFahVcTMMgRjoTkbL8dYmImstmw9oxrahaL8OOji5inMZ+AYP2UGJjUzflWN5SNOWpFGLN5LGHG2tinp1TWt14wCI20XqbVkGsbEwiQeh8Nudb32EgCJ0/Z4dYsa3zBR1jAbklyNv3s8sUnvDN/yvijMJYqxU3kbnHqQhiwt9SohTw445GCv1YlOe7ZqmB1MR2uK+3tRx7WUWpGe8A84uxZEJYMps/S5gxbGUMp5uvIm55S3fEYsWxqus6ssVMUoBAyCaVKCkftfRQBBd2bpBTiiQ8cnAw2BRVaGtDCHKqbZ/FY/uhllUOHxfQxzWwtDh1k3hQgXLnvG8krJytZ4OqZmkWYsOYPgakwZcZeLpBryEJv5CuwG/5RCOM5Cxjlwqph2Kfah0kULNdoAe4GbwvW1bK0PI/9SKZbrhiQ/o6GfR30W9aos3ucNbZihRA9P6er8sWbguzN3qYvdqbrSdrJ+VAyjty7lEBR/BTlUo5NdjbLuYTOhGbhcvCi7TYXThklRjisNzbS1pd/CMIm8difwEudnXmx44+hsqoK8MLgYXXEdSriJuHMeql881pxP2HQjXabr4MJkTKO7hznc4/vNnP+QtNbvjL01f1XDxIUrzsv1D3T45pBYYIfvJGb9rcskyunjJvhyJ2LUAlWY6f3uKUTwBwSIcoEX4V6WuuKYc=
template:
metadata:
creationTimestamp: null
name: mysecret
namespace: default
创建资源对象到集群中
kubectl apply -f mysealedsecret.yml
查看资源对象
kubectl get secret mysecret
输出如下内容:
NAME TYPE DATA AGE
mysecret Opaque 1 17m
查看对象详情
kubectl describe secret mysecret
输出如下内容:
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
foo: 3 bytes
请注意,SealedSecret 和 Secret 必须具有相同的命名空间和名称。这是一个防止同一集群上的其他用户重新使用您的密封机密的功能。
在 Pod 中使用
busybox.yml
文件内容:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: busybox
command:
- cat
- "/etc/mysecret/foo"
volumeMounts:
- name: foo
mountPath: "/etc/mysecret"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
创建这个 Pod:
kubectl apply -f busybox.yml
这个 SealedSecret对象的创建不验证用户。换句话说,任何人都可以创建一个Sealed Secret 对象。
这个和原生的 Secret 对象是一样的设计,都是通过集群的 RBAC 机制控制使用的权限。
管理现有的 Secret
如果希望 Sealed Secrets 控制器管理现有的 Secret,您可以使用 Sealed Secret 对您的 Secret 添加 sealedsecrets.bitnami.com/managed: "true"
annotations(注解)。
当解封具有相同名称和命名空间的 Sealed Secret 时,现有的 Secret 将被覆盖,并且 Sealed Secrets 将拥有该 Secret 的所有权(因此,当删除 Sealed Secret 时,该 Secret 也将被删除)。
例如有一个现有的 Secret 对象 test-secret
, 他的 YAML 文件内容如下:
apiVersion: v1
kind: Secret
metadata:
name: testsecret
data:
foo: MTIz
给原有的 Secret 添加注释
apiVersion: v1
kind: Secret
metadata:
name: testsecret
annotations:
sealedsecrets.bitnami.com/managed: "true"
data:
foo: MTIz
应用一下
kubectl apply -f test-seal.yml
查看运行中的 testsecret 对象的信息
查看运行中的 testsecret 对象 YAML 内容
接着,我创建同名的 Sealed Secrets 对象
这里我偷懒了,没通过创建新的密码创建 Sealed Secrets 对象,而是沿用原来的 Secrets 对象中的密码。
正确的做法是使用新的密码创建 Sealed Secrets 对象去覆盖原来的 Secrets 对象。
kubeseal -f test-secret.yml -w test-seal.yml
kubectl apply -f test-seal.yml
后面就可以使用这个含有加密秘密内容的 YAML 文件 test-seal.yml ,给到 开发使用了,即使可以查看文件中的内容,但因为里面的敏感数据已经被加密,真正的密码也可以得到有效的保护。
并且,此时再查看 Secret 实力对象 testsecret
,会发现对象已经被覆盖。
如果此时,删除 Sealed Secret 对象, 对应的这个 Secret 实例对象 testsecret
也会被删除。
验证密文的有效性
加入你得到一个有加密密文的 YAML 文件
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: testsecret
namespace: default
spec:
encryptedData:
foo: AgAK7TTRNORyvO6nQFq9w0Unj6e+/NHPyipdtbuwdfhyvnZavLpmUIcbHnj/wj/QFvbFiAtc2NoTG2g/sZIZJ8/ZgJi4a7lrNJbCFKcSdxDSyPcAdP0K9SjiSqcpFxw1zVw0NJOtV2q8khcY+aDkk7QWdxio8+0qdSpb5KvbB+eCAByxVdixHJXd8Ul2s/6CqRCkm3DGwwvvvV/1ogoUWMidaZXK/Ju4RopAaKYrnJ0fSpDrR8vRZEC6wJD6HRXc5cXeVXJ8g3SXgAQ1tjRuJkBxF+5S8jODihioBVLbq++6FxXJ/4bepy7NFcY/K7ldnp/B6xngqFiawYuUlPnI+iscE6YkSlSFFGohrgj6xByGVf7jCNBbbOY9itgu8AY/zrAUCedgr8c+xtPfX7cwGlGicD5sw/M1mpRXE0T6uH3H3MTazHk2KZvXTk290pOF6bYrzoV5AsG6I37+fAhDDJs1WFtj47kk0eaiwKJuSPeSVxlQyKqP50oemhK3aXJCOz6ryZUppSD8ufRRqGJls+lN8oFLZakfR6sjYY5zQFLaHIw5vo9egn7dSdUdKch3lP5D4pSohSdHrSYJnEplA4XImRu68X5phJyih6IWPRrOtDv4wZ9+B3qSVMTD5YdyEPirt4azb6pY7Xk4NvCDQctgWP4FRzdjVzu2FNru6t8cC+Gz7fwIJXMr1dkH4W/e8I4UldU=
template:
metadata:
creationTimestamp: null
name: testsecret
namespace: default
如果你想验证文件内的加密密文是否有效,或者是否得到破坏。可以使用如下命令验证
cat test-seal.yml | kubeseal --validate
如果正常,则没有任何返回。如果有问题,则会返回如下内容:
error: unable to decrypt sealed secret: testsecret