前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何解密k8s中的加密资源

如何解密k8s中的加密资源

原创
作者头像
Xkrroeon
发布2022-12-08 14:07:51
1.7K0
发布2022-12-08 14:07:51
举报
文章被收录于专栏:云原生与存储

Overview

k8s是通过kube-apiserver组件将数据存储在etcd中,这些数据通常会通过protobuf或者json序列化后进行存储,而有的资源则需要进行加密存储,比如Secret。本文就以Secret资源为例,手把手教你如何解密K8s集群的加密资源。

被加密的资源

k8s中有的资源在写入etcd之前,是会被进行加密存储的,最常见的就是secret资源。

我们可以通过kube-apiserver的manifests文件中的启动参数可以知道k8s有哪些资源会被加密,比如:

代码语言:javascript
复制
apiVersion: v1
 kind: Pod
 metadata:
 annotations:
   kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.180.130:6443
 creationTimestamp: null
 labels:
   component: kube-apiserver
   tier: control-plane
 name: kube-apiserver
 namespace: kube-system
 spec:
 containers:
 - command:
   - kube-apiserver
   - --advertise-address=192.168.180.130
   - --allow-privileged=true
   ...
   - --encryption-provider-config=/etc/kubernetes/pki/secrets-encryption.yaml
   ...

从上述kube-apiserver的yaml文件可以看到,其中有一个--encryption-provider-config的配置项,该配置项对应的是一个yaml文件路径,而该文件中保存的正是k8s中被加密的资源信息。下面我们看一下该文件中的内容:

代码语言:javascript
复制
kind: EncryptionConfig
 apiVersion: v1
 resources:
 - resources:
   - secrets
   providers:
   - aescbc:
       keys:
       - name: key
         secret: GPG4RC0Vyk7+Mz/niQPttxLIeL4HF96oRCcBRyKNpfM=
   - identity: {}

该yaml文件中记录了k8s中被加密的资源列表及对应的加密算法配置信息:

  1. resources.resources中记录的是被加密的资源列表,可以看到只有secrets资源被加密了。
  2. resources.providers中记录的是对应的加密算法配置信息,可以看到只有aescbc和identity这两种算法配置,其中identity为空,所以secret是通过AES-CBC加密的。除此之外,其实k8s还支持aesgcm、secretbox、kms这几种算法配置。

解析流程

对于这类进行加密存储的k8s资源,我们通常需要进行以下流程来解码,才能最终获得资源的明文信息:

  1. 根据key从etcd中获取被加密的value
  2. 对被加密的value进行解密,得到被解密的value
  3. 通过k8s的解码器对解密后的value进行解码,最终得到资源的明文信息

获取被加密的value

首先,我们需要创建etcd客户端,然后根据资源在etcd中的key来获取value。k8s资源在etcd中的prefix默认为/registry/,如下所示:

代码语言:javascript
复制
 /registry/secrets/rook-ceph/rook-ceph-rgw-token-vd98q
 /registry/secrets/rook-ceph/rook-ceph-system-token-rnpms
 /registry/secrets/rook-ceph/rook-csi-cephfs-plugin-sa-token-n5qdq
 /registry/secrets/rook-ceph/rook-csi-cephfs-provisioner-sa-token-n7947
 /registry/secrets/rook-ceph/rook-csi-rbd-plugin-sa-token-qcknl
 /registry/secrets/rook-ceph/rook-csi-rbd-provisioner-sa-token-wrn6w
 /registry/serviceaccounts/default/default
 /registry/serviceaccounts/kube-node-lease/default
 /registry/serviceaccounts/kube-public/default
 /registry/serviceaccounts/kube-system/attachdetach-controller
 /registry/serviceaccounts/kube-system/bootstrap-signer

这里,我们获取/registry/secrets/rook-ceph/rook-ceph-rgw-token-vd98q这个key的value,而这个key对应的是k8s中rook-ceph这个命名空间下的一个名为rook-ceph-rgw-token-vd98q的secret资源。

创建etcd client

代码语言:javascript
复制
func newEtcdClient() (*clientv3.Client, error) {
  cfgTLS := &transport.TLSInfo{
    CertFile:      "/etc/kubernetes/pki/etcd/server.crt",
    KeyFile:       "/etc/kubernetes/pki/etcd/server.key",
    TrustedCAFile: "/etc/kubernetes/pki/etcd/ca.crt",
  }
 ​
  tlsCfg, err := cfgTLS.ClientConfig()
  if err != nil {
    return nil, err
  }
 ​
  client, err := clientv3.New(clientv3.Config{
    Endpoints:   []string{"192.168.180.130:2379"},
    DialTimeout: 2 * time.Second,
    TLS:         tlsCfg,
  })
  if err != nil {
    return nil, err
  }
 ​
  return client, nil
}

获取value

调用etcd client的Get方法获取key对应的value:

代码语言:javascript
复制
 etcdClient, err := newEtcdClient()
 if err != nil {
  t.Fatal(err)
 }
 ​
 resp, err := etcdClient.Get(context.TODO(), testSecretKey)
 if err != nil {
  t.Fatal(err)
 }
 encryptedData := resp.Kvs[0].Value

这样,我们就得到了被加密的value。

解密value

解密value需要借助k8s中的Transformer来完成,Transformer提供了两个方法:把从etcd中读出的数据进行解密;把即将写入etcd中的数据进行加密。

我们需要按照以下流程对数据进行解密:

  • 首先,我们要根据上述--encryption-provider-config配置项对应的yaml文件,初始化Transformer,每种被加密的resource都会有对应的Transformer:
代码语言:javascript
复制
 transformers, err := encryptionconfig.GetTransformerOverrides("/etc/kubernetes/pki/secrets-encryption.yaml")
 if err != nil {
  t.Fatal(err)
 }
  • 调用Transformer的TransformFromStorage方法来解密:
代码语言:javascript
复制
 gr := schema.ParseGroupResource("secrets")
 transformer := transformers[gr]
 decryptedData, _, err := transformer.TransformFromStorage(encryptedData, authenticatedDataString(testSecretKey))
 if err != nil {
  t.Fatal(err)
 }

至此,我们就得到了经过解密的value了。

解码value

k8s中的资源写入etcd前,通常会经过protobuf或json进行编码。因此,我们还需利用k8s的解码器对解密后的value进行解码。

利用解码器的Decode方法进行解码:

代码语言:javascript
复制
 var secret v1.Secret
 if _, _, err := legacyscheme.Codecs.UniversalDeserializer().Decode(decryptedData, nil, &secret); err != nil {
  t.Fatal(err)
 }
 ​
 unst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&secret)
 if err != nil {
  t.Fatal(err)
 }

最后将secret转成map,主要是为了便于阅读。

完整代码

上述示例的完整代码如下:

代码语言:javascript
复制
 package test
 ​
 import (
 "context"
 "go.etcd.io/etcd/clientv3"
 "go.etcd.io/etcd/pkg/transport"
 v1 "k8s.io/api/core/v1"
 "k8s.io/apimachinery/pkg/runtime"
 "k8s.io/apimachinery/pkg/runtime/schema"
 "k8s.io/apiserver/pkg/server/options/encryptionconfig"
 "k8s.io/apiserver/pkg/storage/value"
 "k8s.io/kubernetes/pkg/api/legacyscheme"
 "testing"
 "time"
 )
 ​
 func newEtcdClient() (*clientv3.Client, error) {
  cfgTLS := &transport.TLSInfo{
   CertFile:      "/etc/kubernetes/pki/etcd/server.crt",
   KeyFile:       "/etc/kubernetes/pki/etcd/server.key",
   TrustedCAFile: "/etc/kubernetes/pki/etcd/ca.crt",
  }
 ​
  tlsCfg, err := cfgTLS.ClientConfig()
  if err != nil {
   return nil, err
  }
 ​
  client, err := clientv3.New(clientv3.Config{
   Endpoints:   []string{"192.168.180.130:2379"},
   DialTimeout: 2 * time.Second,
   TLS:         tlsCfg,
  })
  if err != nil {
   return nil, err
  }
 ​
  return client, nil
 }
 ​
 type authenticatedDataString string
 ​
 // AuthenticatedData implements the value.Context interface.
 func (d authenticatedDataString) AuthenticatedData() []byte {
  return []byte(d)
 }
 ​
 var _ value.Context = authenticatedDataString("")
 ​
 func TestSecretDecode(t *testing.T) {
  var testSecretKey = "/registry/secrets/rook-ceph/rook-ceph-rgw-token-vd98q"
 ​
  etcdClient, err := newEtcdClient()
  if err != nil {
   t.Fatal(err)
  }
 ​
  resp, err := etcdClient.Get(context.TODO(), testSecretKey)
  if err != nil {
   t.Fatal(err)
  }
  encryptedData := resp.Kvs[0].Value
 ​
  transformers, err := encryptionconfig.GetTransformerOverrides("/etc/kubernetes/pki/secrets-encryption.yaml")
  if err != nil {
   t.Fatal(err)
  }
  gr := schema.ParseGroupResource("secrets")
  transformer := transformers[gr]
  decryptedData, _, err := transformer.TransformFromStorage(encryptedData, authenticatedDataString(testSecretKey))
  if err != nil {
   t.Fatal(err)
  }
 ​
  var secret v1.Secret
  if _, _, err := legacyscheme.Codecs.UniversalDeserializer().Decode(decryptedData, nil, &secret); err != nil {
   t.Fatal(err)
  }
 ​
  unst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&secret)
  if err != nil {
   t.Fatal(err)
  }
  t.Log(unst)
 }

总结

本文主要是熟悉K8s资源在etcd中的存储方式:

K8s是通过kube-apiserver组件将数据存储在etcd中;

这些数据在写入etcd之前通常会通过protobuf或者json进行序列化;

有的数据在序列化之后是需要加密存储的,这些加密资源是kube-apiserver组件通过--encryption-provider-config参数指定的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Overview
  • 被加密的资源
  • 解析流程
  • 获取被加密的value
  • 创建etcd client
  • 获取value
  • 解密value
  • 解码value
  • 完整代码
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档