ErdemOzgen/kubernetes-kms-encryption-demo

GitHub: ErdemOzgen/kubernetes-kms-encryption-demo

一个基于 k3d 和自定义 Go KMIP KMS 的本地实验环境,用于演示和验证 Kubernetes KMS v2 静态加密机制。

Stars: 1 | Forks: 0

# 使用 Go KMIP KMS 进行 k3d Kubernetes 加密 本仓库是一个可在本地 k3d 集群中运行 Kubernetes 静态加密的概念验证。 它将以下各个部分串联在一起: ``` kubectl | v kube-apiserver inside k3d | | Kubernetes KMS v2 gRPC over Unix socket | unix:///var/run/kmsplugin/okms.sock v okms-k8s-encryption-provider | | KMIP over mTLS | go-kmip-kms:5696 v Go KMIP KMS in this repo | v Persistent AES-256-GCM KEK data/poc-kek-1.bin ``` 核心概念:Kubernetes 并不直接使用 KMIP 通信。Kubernetes 通过 Kubernetes KMS 插件协议与本地 Unix socket 通信。在此 PoC 中,由 OVH 的 `okms-k8s-encryption-provider` 接管该 socket,并将 Kubernetes KMS 请求转换为针对 Go KMS 的 KMIP 调用。 ## 架构图 ### 组件关系 ``` flowchart LR user["User / kubectl"] subgraph k3d["k3d cluster: dev"] lb["k3d load balancer"] subgraph cp["k3d server / Kubernetes control plane"] apiserver["kube-apiserver"] encconfig["EncryptionConfiguration
/etc/kubernetes/enc/encryption-config.yaml"] socketmount["Mounted socket dir
/var/run/kmsplugin"] etcd["embedded etcd
raw Kubernetes datastore"] end agent["k3d agent"] end subgraph compose["Docker Compose KMS stack"] okms["okms-k8s-encryption-provider
Kubernetes KMS v2 plugin"] kmipkms["go-kmip-kms
custom Go KMIP server"] kek["data/poc-kek-1.bin
persistent AES 256 GCM KEK"] certs["certs/
local CA, server cert, client cert"] sock["run/kmsplugin/okms.sock
host Unix socket"] end user -->|"kubectl API requests"| lb lb --> apiserver apiserver -->|"loads config"| encconfig apiserver -->|"stores and reads objects"| etcd apiserver -->|"KMS v2 gRPC
unix:///var/run/kmsplugin/okms.sock"| socketmount socketmount -. "host bind mount" .- sock sock --> okms okms -->|"KMIP over mTLS
go-kmip-kms:5696"| kmipkms kmipkms -->|"loads / creates"| kek okms -. "client certificate" .- certs kmipkms -. "server certificate and CA" .- certs ``` ### 协议边界 ``` flowchart TB kubernetes["Kubernetes control plane"] plugin["okms-k8s-encryption-provider"] kms["Go KMIP KMS"] keyfile["Persistent KEK file"] kubernetes -->|"Kubernetes KMS v2
gRPC over Unix socket"| plugin plugin -->|"KMIP 1.x
TLS / mutual TLS"| kms kms -->|"local file I/O
32-byte AES key"| keyfile note1["Kubernetes never talks KMIP directly"] note2["okms is the bridge between Kubernetes KMS v2 and KMIP"] note3["The Go KMS never talks to Kubernetes directly"] kubernetes -.-> note1 plugin -.-> note2 kms -.-> note3 ``` ### 启动依赖顺序 ``` flowchart TD start["./k3dup-kms.sh"] dirs["Create certs/, data/, run/kmsplugin/"] certgen["Generate mTLS certs if missing"] composeup["docker compose up -d --build"] kmsup["go-kmip-kms listens on :5696"] okmsup["okms connects to KMIP KMS"] validatekey["okms validates key with Get and GetAttributes"] sockready["okms creates run/kmsplugin/okms.sock"] k3dcreate["k3d cluster create"] mountsocket["Mount socket dir into k3d server"] mountconfig["Mount encryption config into k3d server"] apiserverready["kube-apiserver starts with KMS v2 config"] start --> dirs --> certgen --> composeup composeup --> kmsup --> okmsup --> validatekey --> sockready sockready --> k3dcreate k3dcreate --> mountsocket k3dcreate --> mountconfig mountsocket --> apiserverready mountconfig --> apiserverready ``` ## 仓库结构 ``` . ├── docker-compose.yaml ├── k3dup-kms.sh ├── k3down-kms.sh ├── encryption/ │ └── encryption-config.yaml ├── kms/ │ ├── Dockerfile │ ├── go.mod │ ├── cmd/kms/main.go │ └── internal/pockms/ │ ├── kms.go │ └── kms_test.go ├── okms-provider/ │ └── Dockerfile ├── scripts/ │ ├── clean-all.sh │ ├── clean-docker.sh │ ├── clean-generated.sh │ ├── clean-k8s-test-objects.sh │ ├── gen-certs.sh │ ├── validate.sh │ └── wait-okms-socket.sh ├── certs/ ├── data/ └── run/kmsplugin/ ``` 生成的本地状态: - `certs/`:用于 KMIP mTLS 的本地 CA、服务器证书和客户端证书。 - `data/poc-kek-1.bin`:Go KMS 使用的持久化 32 字节 KEK。 - `run/kmsplugin/okms.sock`:由 okms 创建并挂载到 k3d 服务器中的 Unix socket。 除了 `.gitkeep` 占位符外,这些路径均被 Git 忽略。 ## 快速开始 前置条件: - 支持 Compose 的 Docker - k3d - kubectl - openssl - Go(如果你想运行本地单元测试) 创建完整的 PoC: ``` ./k3dup-kms.sh ``` 验证加密: ``` ./scripts/validate.sh ``` 运行 Go 测试: ``` cd kms go test ./... ``` 销毁 PoC: ``` ./k3down-kms.sh ``` 清理生成的工作文件,同时保留 KEK: ``` ./scripts/clean-generated.sh ``` ## Kubernetes 静态加密的作用 Kubernetes 将 API 对象存储在其数据存储中。对于使用 `--cluster-init` 的 k3s,该数据存储是内嵌的 etcd。 静态加密会在选定的 API 资源写入该数据存储之前对其进行保护。在此 PoC 中,仅对以下资源进行加密: - `secrets` - `configmaps` 配置文件为 [encryption/encryption-config.yaml](encryption/encryption-config.yaml): ``` apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets - configmaps providers: - kms: apiVersion: v2 name: okms-encryption-provider endpoint: unix:///var/run/kmsplugin/okms.sock timeout: 3s - identity: {} ``` Provider 的顺序非常重要: 1. 新写入的数据使用第一个 provider,即 `kms`。 2. `identity` 是针对仍以未加密方式存储的对象的后备选项。 3. 一旦对象使用 KMS 写入,存储的值将包含一个 Kubernetes 加密前缀和加密后的字节。 这不适用于加密以下内容: - pod 内部的文件 - 挂载的卷 - 容器镜像 - 节点磁盘 - 应用流量 - Kubernetes API 资源之外的数据 对于 pod 文件系统或卷加密,请使用其他方式:加密卷、CSI 驱动程序加密、应用层加密或节点/磁盘加密。 ## Kubernetes KMS v2 的工作原理 Kubernetes KMS v2 使用信封加密。 宏观流程如下: 1. 用户创建或更新 API 对象,例如 Secret。 2. kube-apiserver 将该对象序列化以便存储。 3. 存储加密层发现 `secrets` 已配置为使用 KMS 加密。 4. Kubernetes 使用数据加密密钥(DEK)来加密对象数据。 5. KMS 插件使用外部的密钥加密密钥(KEK)来保护该 DEK。 6. 加密后的对象数据连同加密的密钥材料被存入 etcd。 7. 读取时,kube-apiserver 执行逆向流程,并通过 Kubernetes API 返回明文。 如果用户拥有授权,他们依然可以通过 `kubectl` 看到明文: ``` kubectl get secret poc-secret -o jsonpath='{.data.password}' | base64 -d ``` 但直接检查原始数据存储时,不应该看到原始值: ``` s3cr3t token=abc ``` 相反,原始的 etcd 数据中应包含类似如下前缀: ``` k8s:enc:kms:v2:okms-encryption-provider ``` 该前缀证明 Kubernetes 是通过配置的 KMS v2 provider 存储该资源的。 ## 为什么 okms 要在 Kubernetes 之外运行 kube-apiserver 在启动时就需要 KMS 插件的 socket。 如果将该插件部署为普通的 Kubernetes Deployment,API server 将不得不等待集群健康运行后才能启动插件。但是,API server 在安全地读写加密资源之前又需要该插件。这就产生了一个引导启动问题。 本 PoC 通过在创建 k3d 集群之前使用 Docker Compose 运行 okms 来避免此问题。 完整流程如下: 1. 启动 Go KMIP KMS。 2. 启动 okms。 3. 等待 `run/kmsplugin/okms.sock` 生成。 4. 创建 k3d 集群。 5. 将 `run/kmsplugin` 挂载到 k3d 服务器的 `/var/run/kmsplugin` 目录。 6. 将 kube-apiserver 指向 `unix:///var/run/kmsplugin/okms.sock`。 ## Docker Compose 服务 [docker-compose.yaml](docker-compose.yaml) 定义了两个服务。 ### `go-kmip-kms` 这是本仓库中实现的自定义 Go KMS。 重要的环境变量: ``` KMS_KEY_ID: poc-kek-1 KMS_LISTEN: ":5696" KMS_CERT: /certs/server.crt KMS_KEY: /certs/server.key KMS_CA: /certs/ca.crt KMS_KEK_FILE: /data/poc-kek-1.bin ``` 卷: - `./certs:/certs:ro` - `./data:/data` KMS 在 TCP `5696` 端口上监听并使用 mTLS。 ### `okms-provider` 即 `okms-k8s-encryption-provider`。 它执行三个重要操作: 1. 通过将 `certs/ca.crt` 复制到容器信任存储中来信任生成的本地 CA。 2. 通过 KMIP/mTLS 连接到 `go-kmip-kms:5696`。 3. 为 kube-apiserver 创建 `/var/run/kmsplugin/okms.sock`。 命令: ``` okms-k8s-encryption-provider \ --protocol kmip \ --serv-addr go-kmip-kms:5696 \ --encryption-key-id poc-kek-1 \ --client-cert /certs/client.crt \ --client-key /certs/client.key \ --sock /var/run/kmsplugin/okms.sock \ --timeout 10s \ --debug ``` 该 provider 版本未公开 `--ca` 标志,因此在 provider 启动之前,CA 会被安装到容器的信任存储中。 ## Go KMIP KMS KMS 的实现位于 [kms/internal/pockms/kms.go](kms/internal/pockms/kms.go)。 它支持以下 KMIP 操作: | 操作 | 存在原因 | | --- | --- | | `DiscoverVersions` | 允许客户端协商支持的 KMIP 版本。 | | `Query` | 报告支持的操作和对象类型。 | | `Get` | 允许 okms 进行验证以确认密钥存在。 | | `GetAttributes` | 允许 okms 进行验证以确认是 AES 且具有加密/解密用途。 | | `Encrypt` | 使用 AES-256-GCM 加密来自 okms 的明文。 | | `Decrypt` | 使用 AES-256-GCM 解密来自 okms 的密文。 | 密钥设置特意保持简单且固定: ``` Key ID: poc-kek-1 Algorithm: AES 256 GCM KEK size: 32 bytes Nonce size: 12 bytes Tag size: 16 bytes ``` KEK 从以下位置加载: ``` data/poc-kek-1.bin ``` 如果文件不存在,KMS 会创建一个新的 32 字节随机密钥。如果文件存在但长度不正好是 32 字节,启动将会失败。 这是有意为之的。如果该 KEK 发生更改或丢失,使用旧 KEK 加密的 Kubernetes 数据将无法被解密。 ## KMIP 加密与解密的数据结构 对于 `Encrypt`,okms 大致发送如下内容: ``` UniqueIdentifier = poc-kek-1 Data = plaintext IVCounterNonce = nonce AuthenticatedEncryptionAdditionalData = optional AAD ``` KMS 执行操作: 1. 检查 key ID。 2. 使用持久化的 AES-256 KEK。 3. 使用提供的 nonce 或创建一个 12 字节的 nonce。 4. 执行 AES GCM 封装。 5. 分别返回 ciphertext 和 authentication tag。 对于 `Decrypt`,okms 发送如下内容: ``` UniqueIdentifier = poc-kek-1 Data = ciphertext IVCounterNonce = nonce AuthenticatedEncryptionTag = tag AuthenticatedEncryptionAdditionalData = optional AAD ``` KMS 执行操作: 1. 检查 key ID。 2. 将 `ciphertext || tag` 重新组合。 3. 执行 AES GCM 解密。 4. 返回 plaintext。 如果 nonce、tag、AAD、ciphertext 或 KEK 不正确,AES GCM 身份验证将失败。 ## 证书与 mTLS [scripts/gen-certs.sh](scripts/gen-certs.sh) 创建了: ``` certs/ca.crt certs/ca.key certs/server.crt certs/server.key certs/client.crt certs/client.key ``` 服务器证书包含此 SAN: ``` DNS:go-kmip-kms DNS:localhost IP:127.0.0.1 ``` 此处必须包含 `go-kmip-kms`,因为这是 okms provider 使用的 Docker Compose DNS 名称。 Go KMS 要求并验证客户端证书: ``` ClientAuth: tls.RequireAndVerifyClientCert ``` 因此该连接属于双向 TLS: - okms 验证 KMS 服务器证书。 - Go KMS 验证 okms 客户端证书。 重新生成证书: ``` FORCE=1 ./scripts/gen-certs.sh ``` 如果你在容器运行时重新生成证书,请重启 Compose 服务: ``` docker compose up -d --build ``` ## k3d 安装脚本 [k3dup-kms.sh](k3dup-kms.sh) 是主要的安装入口。 它执行以下操作: 1. 检查 `docker`、`k3d`、`kubectl` 和 `openssl`。 2. 创建 `certs`、`data` 和 `run/kmsplugin` 目录。 3. 如果缺失则生成证书。 4. 通过 `docker compose up -d --build` 启动 Compose。 5. 等待 `run/kmsplugin/okms.sock` 生成。 6. 如果 k3d 集群不存在则创建它。 7. 将 KMS socket 目录挂载到 k3d 服务器中。 8. 将加密配置挂载到 k3d 服务器中。 9. 通过 k3s 传递 kube-apiserver 加密标志。 重要的 k3d 参数: ``` --k3s-arg "--cluster-init@server:0" --volume "$KMS_ROOT/run/kmsplugin:/var/run/kmsplugin@server:0" --volume "$KMS_ROOT/encryption:/etc/kubernetes/enc@server:0" --k3s-arg "--kube-apiserver-arg=encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml@server:0" --k3s-arg "--kube-apiserver-arg=encryption-provider-config-automatic-reload=true@server:0" ``` `--cluster-init` 会启用内嵌的 etcd,这使得直接验证原始数据存储成为可能。 可用的覆盖参数: ``` CLUSTER_NAME=dev REGISTRY_NAME=dev-registry REGISTRY_PORT=5111 API_PORT=6550 KMS_ROOT="$PWD" ``` 示例: ``` CLUSTER_NAME=kmsdemo API_PORT=6551 ./k3dup-kms.sh ``` ## k3d 卸载脚本 [k3down-kms.sh](k3down-kms.sh) 是配套的卸载脚本。 默认情况下,它会: 1. 删除由 `CLUSTER_NAME` 指定的 k3d 集群。 2. 停止 Docker Compose 的 KMS 和 okms provider 栈。 3. 移除过期的 `run/kmsplugin/okms.sock` socket 文件。 4. 保留已生成的证书和 `data/poc-kek-1.bin`。 KEK 被特意保留下来。删除 `data/poc-kek-1.bin` 将导致使用该 KEK 加密的旧 Kubernetes 数据无法解密。 运行常规卸载: ``` ./k3down-kms.sh ``` 可用的覆盖参数: ``` CLUSTER_NAME=dev KMS_ROOT="$PWD" KEEP_CLUSTER=0 KEEP_COMPOSE=0 PRUNE_GENERATED=0 ``` 保留集群但停止 Compose: ``` KEEP_CLUSTER=1 ./k3down-kms.sh ``` 删除集群和 Compose 服务,同时删除生成的证书和 KEK: ``` PRUNE_GENERATED=1 ./k3down-kms.sh ``` 仅当你不再需要由此 PoC 加密的任何数据时,才使用 `PRUNE_GENERATED=1`。 ## 清理脚本 清理脚本按职责进行了拆分,以便你可以选择要移除的状态量。 ### 生成的文件 [scripts/clean-generated.sh](scripts/clean-generated.sh) 清理本地生成的文件。 默认情况下,它会: - 移除 `run/kmsplugin/okms.sock` - 保留生成的证书 - 保留 `data/poc-kek-1.bin` 运行安全的清理操作: ``` ./scripts/clean-generated.sh ``` 移除证书的 CSR、扩展文件和序列号文件: ``` PRUNE_CERT_WORKFILES=1 ./scripts/clean-generated.sh ``` 同时移除生成的证书: ``` PRUNE_CERTS=1 ./scripts/clean-generated.sh ``` 移除持久化的 KEK: ``` PRUNE_KEK=1 ./scripts/clean-generated.sh ``` 仅当旧的加密 Kubernetes 数据不再重要时,才使用 `PRUNE_KEK=1`。 ### Kubernetes 测试对象 [scripts/clean-k8s-test-objects.sh](scripts/clean-k8s-test-objects.sh) 移除用于验证的 Secret 和 ConfigMap: ``` ./scripts/clean-k8s-test-objects.sh ``` 默认操作: ``` NAMESPACE=default SECRET_NAME=poc-secret CONFIGMAP_NAME=poc-config ``` ### Docker 资源 [scripts/clean-docker.sh](scripts/clean-docker.sh) 为 KMS 和 okms provider 栈执行 Compose 清理: ``` ./scripts/clean-docker.sh ``` 默认情况下,它运行: ``` docker compose down --remove-orphans ``` 同时移除 Compose 卷: ``` REMOVE_VOLUMES=1 ./scripts/clean-docker.sh ``` 同时移除本地构建的 Compose 镜像: ``` REMOVE_IMAGES=1 ./scripts/clean-docker.sh ``` ### 完整清理包装脚本 [scripts/clean-all.sh](scripts/clean-all.sh) 整合了所有的清理步骤。 默认情况下,它会: - 跳过 Kubernetes 测试对象的清理 - 删除 k3d 集群 - 停止 Compose - 移除过期的 socket 状态 - 移除证书工作文件 - 保留生成的证书 - 保留 `data/poc-kek-1.bin` - 保留本地构建的 Docker 镜像 运行默认的完整清理: ``` ./scripts/clean-all.sh ``` 在卸载前同时删除验证测试对象: ``` CLEAN_K8S_TEST_OBJECTS=1 ./scripts/clean-all.sh ``` 同时移除生成的证书和 KEK: ``` PRUNE_CERTS=1 PRUNE_KEK=1 ./scripts/clean-all.sh ``` 同时移除本地构建的 Compose 镜像: ``` REMOVE_IMAGES=1 ./scripts/clean-all.sh ``` ## 验证脚本 [scripts/validate.sh](scripts/validate.sh) 用于证明整个链路运行正常。 它执行以下操作: 1. 如果存在旧的测试对象,则将其删除。 2. 创建一个 Secret: kubectl create secret generic poc-secret --from-literal=password='s3cr3t' 3. 创建一个 ConfigMap: kubectl create configmap poc-config --from-literal=app.conf='token=abc' 4. 通过 Kubernetes API 读取这两个对象,并验证返回的是明文。 5. 直接读取原始的 etcd 值。 6. 确认原始值包含: k8s:enc:kms:v2:okms-encryption-provider 7 确认原始 etcd 数据不包含: s3cr3t token=abc 8. 重启 `go-kmip-kms`。 9. 再次读取这些对象,以证明持久化的 KEK 仍然可以解密旧数据。 该脚本首先会尝试在 k3d 服务器容器内使用 `etcdctl`。如果不可用,它会使用: ``` quay.io/coreos/etcd:v3.5.18 ``` 你可以覆盖该设置: ``` ETCDCTL_IMAGE=quay.io/coreos/etcd:v3.5.18 ./scripts/validate.sh ``` ## 此 PoC 中的读写流程 ### 写入路径 ``` sequenceDiagram autonumber participant User as kubectl / user participant API as kube-apiserver participant Enc as encryption provider chain participant OKMS as okms KMS v2 plugin participant KMIP as Go KMIP KMS participant KEK as data/poc-kek-1.bin participant Etcd as embedded etcd User->>API: create or update Secret / ConfigMap API->>Enc: serialize object for storage Enc->>Enc: match resource against EncryptionConfiguration Enc->>OKMS: KMS v2 Encrypt over Unix socket OKMS->>KMIP: KMIP Encrypt over mTLS
UniqueIdentifier = poc-kek-1 KMIP->>KEK: load persistent AES-256 KEK KMIP->>KMIP: AES GCM seal plaintext KMIP-->>OKMS: ciphertext + nonce + auth tag OKMS-->>Enc: encrypted envelope material Enc->>Etcd: store value with k8s:enc:kms:v2 prefix API-->>User: Kubernetes API write succeeds ``` ### 读取路径 ``` sequenceDiagram autonumber participant User as kubectl / user participant API as kube-apiserver participant Etcd as embedded etcd participant Enc as encryption provider chain participant OKMS as okms KMS v2 plugin participant KMIP as Go KMIP KMS participant KEK as data/poc-kek-1.bin User->>API: get Secret / ConfigMap API->>Etcd: read stored object bytes Etcd-->>API: encrypted value with KMS v2 prefix API->>Enc: choose decrypt provider from prefix Enc->>OKMS: KMS v2 Decrypt over Unix socket OKMS->>KMIP: KMIP Decrypt over mTLS
ciphertext + nonce + tag KMIP->>KEK: load same persistent KEK KMIP->>KMIP: AES GCM open and authenticate KMIP-->>OKMS: plaintext OKMS-->>Enc: decrypted envelope material Enc-->>API: decrypted Kubernetes object API-->>User: plaintext API response if RBAC allows it ``` ### 验证路径 ``` flowchart TD validate["./scripts/validate.sh"] create["Create poc-secret and poc-config"] apiRead["Read through Kubernetes API"] plaintextCheck{"API returns plaintext?"} rawRead["Read raw etcd keys"] prefixCheck{"Raw value has
k8s:enc:kms:v2 prefix?"} leakCheck{"Raw value omits
s3cr3t and token=abc?"} restart["Restart go-kmip-kms"] reread["Read objects again"] persistentCheck{"Still decrypts after restart?"} pass["Validation passed"] fail["Validation failed"] validate --> create --> apiRead --> plaintextCheck plaintextCheck -- yes --> rawRead plaintextCheck -- no --> fail rawRead --> prefixCheck prefixCheck -- yes --> leakCheck prefixCheck -- no --> fail leakCheck -- yes --> restart leakCheck -- no --> fail restart --> reread --> persistentCheck persistentCheck -- yes --> pass persistentCheck -- no --> fail ``` 写入流程: ``` kubectl create secret -> kube-apiserver -> encryption provider chain -> KMS v2 provider -> Unix socket /var/run/kmsplugin/okms.sock -> okms provider -> KMIP Encrypt over mTLS -> Go KMS -> AES GCM using data/poc-kek-1.bin -> encrypted bytes stored in etcd ``` 读取流程: ``` kubectl get secret -> kube-apiserver -> reads encrypted bytes from etcd -> sees k8s:enc:kms:v2 prefix -> KMS v2 provider -> okms Unix socket -> KMIP Decrypt over mTLS -> Go KMS -> AES GCM open using data/poc-kek-1.bin -> plaintext returned through Kubernetes API ``` 原始数据存储读取: ``` etcdctl get /registry/secrets/default/poc-secret ``` 这应该显示为加密存储的数据,而不是用户的明文。 ## 为什么配置中仍然保留 `identity` `identity` provider 意味着“不加密”。 将其放在 KMS 之后,是为了让 Kubernetes 依然能够读取在启用加密之前写入的对象。 这并不意味着新对象会以未加密方式存储。新的写入操作会使用列表中的第一个 provider,即 `kms`。 ## 密钥持久化与故障模式 此 PoC 中最重要的文件是: ``` data/poc-kek-1.bin ``` 如果保留此文件: - KMS 重启不会出现问题。 - Docker 容器重建不会出现问题。 - 现有的加密 Kubernetes 对象依然可以解密。 如果此文件被删除或替换: - 该 KMS 将无法解密旧的加密 Kubernetes 对象 - 读取这些加密对象的 API 请求可能会失败 - 如果关键资源被加密了,集群可能会变得难以操作 验证脚本显式地重启了 KMS,以证明此基于文件的 KEK 是有效的。 ## 此 PoC 的局限性 这不是生产级别的 KMS 软件。 未实现的功能: - 多个 KEK - 密钥轮换 - 用于标签查找的 KMIP `Locate` - 通过 KMIP 创建密钥 - 密钥销毁或激活生命周期 - 审计日志 - HSM 集成 - 高可用性 - 备份/恢复自动化 - 生产级别的证书管理 - 安全的密钥归零 它是有意设计的一个小型端到端概念验证,旨在证明: ``` Kubernetes API resource -> kube-apiserver KMS v2 -> okms Unix socket -> KMIP over mTLS -> Go AES GCM KMS -> encrypted etcd value ``` ## 常用命令 启动或更新 Compose 服务: ``` docker compose up -d --build ``` 检查服务状态: ``` docker compose ps ``` 查看日志: ``` docker compose logs -f go-kmip-kms okms-provider ``` 检查 socket: ``` test -S run/kmsplugin/okms.sock && echo "okms socket ready" ``` 运行完整的集群设置: ``` ./k3dup-kms.sh ``` 卸载 PoC: ``` ./k3down-kms.sh ``` 运行完整的清理包装脚本: ``` ./scripts/clean-all.sh ``` 仅清理生成的本地文件: ``` ./scripts/clean-generated.sh ``` 验证: ``` ./scripts/validate.sh ``` 运行测试: ``` cd kms go test ./... ``` 仅删除集群: ``` k3d cluster delete dev ``` 仅停止 Compose: ``` docker compose down ``` 使用辅助脚本清理 Compose 资源: ``` ./scripts/clean-docker.sh ``` ## 故障排除 ### `okms socket 未出现` 检查日志: ``` docker compose logs --tail=100 go-kmip-kms okms-provider ``` 常见原因: - 未生成证书 - `server.crt` 对 `go-kmip-kms` 无效 - okms 无法信任该 CA - Go KMS 启动失败 - `data/poc-kek-1.bin` 存在但大小不是 32 字节 ### `failed to retrieve kmip key id` 或验证错误 okms provider 在处理 Kubernetes 请求之前会验证密钥。 因此本 PoC 支持 `Get` 和 `GetAttributes`。`GetAttributes` 必须报告: - `Cryptographic Algorithm = AES` - `Cryptographic Usage Mask = Encrypt | Decrypt` ### Kubernetes API server 在启动期间失败 配置了加密功能时,kube-apiserver 需要 KMS socket。 在创建集群之前,确保以下内容存在: ``` run/kmsplugin/okms.sock ``` 如果在添加 socket/配置挂载之前就已经创建了集群,请重新创建它: ``` k3d cluster delete dev ./k3dup-kms.sh ``` ### `etcdctl: 未找到` 某些 k3s 镜像不包含 `etcdctl`。 验证脚本通过在 k3d 服务器的网络命名空间中运行一个外部 etcd 镜像来处理此问题。它会将 etcd 客户端证书从服务器容器中提取到一个临时目录,并将其挂载到 etcdctl 容器中。 ### `原始 etcd 值仍然包含明文` 可能的原因: - 该资源是在启用加密之前创建的 - kube-apiserver 未加载加密配置 - KMS provider 没有排在 provider 列表的第一位 - 你检查了错误的集群或命名空间 在启用加密后重新写入该对象: ``` kubectl get secret poc-secret -o json | kubectl replace -f - kubectl get configmap poc-config -o json | kubectl replace -f - ``` ### 现有集群已存在 `k3dup-kms.sh` 不会将现有集群变更为启用 KMS 的集群。 如果 `dev` 已经存在,且在创建时没有添加这些挂载和 kube-apiserver 参数,请将其删除并重新创建: ``` k3d cluster delete dev ./k3dup-kms.sh ``` ## 参考来源 - Kubernetes 静态加密: - Kubernetes KMS provider: - OVH okms Kubernetes encryption provider: - `kmip-go`:
标签:EVTX分析, Go, k3d, KMIP, KMS, Ruby工具, 子域名突变, 数据加密, 日志审计, 版权保护