分类 工具盒 下的文章

使用 openssl x509 -text 解析带有 Unicode 字符的证书时,你会看到如下格式的输出。

Subject: C = TR, L = Gebze - Kocaeli, O = T\C3\BCrkiye Bilimsel ve Teknolojik Ara\C5\9Ft\C4\B1rma Kurumu - T\C3\9CB\C4\B0TAK, OU = Ulusal Elektronik ve Kriptoloji Ara\C5\9Ft\C4\B1rma Enstit\C3\BCs\C3\BC - UEKAE, OU = Kamu Sertifikasyon Merkezi, CN = T\C3\9CB\C4\B0TAK UEKAE K\C3\B6k Sertifika Hizmet Sa\C4\9Flay\C4\B1c\C4\B1s\C4\B1 - S\C3\BCr\C3\BCm 3

你可能会好奇这种 \C3 的转义方式是什么,查阅 OpenSSL 的 man 手册可以发现默认的显示格式为 oneline,相当于 RFC 2253 格式。

oneline

Display the name in one line, using a format that is more readable RFC 2253. It is equivalent to esc\_2253, esc\_ctrl, esc\_msb, utf8, dump\_nostr, dump\_der, use\_quote, sep\_comma\_plus\_space, space\_eq and sname options.

https://www.openssl.org/docs/man3.0/man1/openssl-namedisplay-options.html

从而找到对应的 RFC 文档

Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names

https://datatracker.ietf.org/doc/html/rfc2253

还有其继任者 RFC 4514,这里就不展开了。那么要如何将这种 escape 的格式转为正常的 UTF-8 字符串呢?

参考 https://www.python-ldap.org/en/python-ldap-3.4.0/reference/ldap-dn.html,只需要安装 python-ldap 包后执行

import ldap.dn

s = r'C = TR, L = Gebze - Kocaeli, O = T\C3\BCrkiye Bilimsel ve Teknolojik Ara\C5\9Ft\C4\B1rma Kurumu - T\C3\9CB\C4\B0TAK, OU = Ulusal Elektronik ve Kriptoloji Ara\C5\9Ft\C4\B1rma Enstit\C3\BCs\C3\BC - UEKAE, OU = Kamu Sertifikasyon Merkezi, CN = T\C3\9CB\C4\B0TAK UEKAE K\C3\B6k Sertifika Hizmet Sa\C4\9Flay\C4\B1c\C4\B1s\C4\B1 - S\C3\BCr\C3\BCm 3'

ldap.dn.str2dn(s)
# Output:
# [[('C', 'TR', 1)], [('L', 'Gebze - Kocaeli', 1)], [('O', 'Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK', 4)], [('OU', 'Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE', 4)], [('OU', 'Kamu Sertifikasyon Merkezi', 1)], [('CN', 'TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3', 4)]]

就能获得原始字符串了。


以上示例使用的证书:

-----BEGIN CERTIFICATE-----
MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
yZyQ2uypQjyttgI=
-----END CERTIFICATE-----

说到输入法当然要用开源的了,不然谁知道自己的打字记录会在云端保留多久。我在用的是 Rime 输入法+四叶草拼音输入方案+小鹤双拼,不过四叶草字库里生僻字太多,翻页翻过头时就全变成无法识别的方块了(没有安装支持过于生僻字的字体)。

字的频度值越低,说明字越生僻。所以可以设置一个最低频度来控制候选词里的生僻字数。先把频度值都取出来:

awk -F\\t '$3!="" {print $3}' data/clover.base.dict.yaml > freq.txt

数据分析用 Jupyter 笔记本比较方便。安装上 VSCode 的 Jupyter 插件,在 VSCode 里选择有 seaborn 和 ipykernel 包的 Python 环境就可以开始分析了。

首先把需要的不需要的依赖都导入进来,免得后面再找。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

然后把之前提取出的词频文件用 pandas 导入

fl = pd.read_csv('freq.txt', names=['cnt'], header=None)
df = fl['cnt']

成功后就可以开始画图分析了,先画个分布图。

sns.displot(df)

然而执行了好几分钟还没跑出来结果。回头看一眼数据,最大值 579392145,最小值 0。这个分布太散直接拿来画图确实不合理,那就取个对数试试。为了后面好过滤就以 10 为底吧。

df_log = df.transform(np.log10)
sns.displot(df_log)

output1.png

这次 OK 了,可以看出个位数频度里有不少字,放大看下

df_less_than_10 = df.where(lambda x : x < 10).dropna()
sns.displot(df_less_than_10)

output2.png

三万多字集中在 1 上,回到 yaml 文件里一看全是生僻字,直接过滤了吧。

df_log_main = df_log.where(lambda x : x > 1).dropna()
sns.displot(df_log_main)

output3.png

这就好看多了。大部分字还是集中在 10e4 到 10e6 范围内。那就从频度 10000 往下翻看有没有需要保留的字。

$ cd data
$ less clover.base.dict.yaml
<用 ^I...$ 搜索定位到 1000 以下>
$ grep '扁' clover.base.dict.yaml
扁    bian    255939
扁    pian    661

看来保留到 600 字频应该就差不多了,剩下的要么是生僻字,要么是多音字很少见的读音。

diff --git a/src/clover-dict-gen b/src/clover-dict-gen
index 4ef8716..87b74a8 100755
--- a/src/clover-dict-gen
+++ b/src/clover-dict-gen
@@ -186,12 +186,13 @@ class DictGenerator:
         return (word_count, parse_count)
 
 
-    def getWordDictText(self):
+    def getWordDictText(self, min_freq = -1):
         """
             生成单字的 rime 字典文本
         """
         # 按频率倒序排序
         word_list = [(key[0], key[1], self.word_dict[key]) for key in self.word_dict]
+        word_list = list(filter(lambda w: w[2] > min_freq, word_list))
         word_list.sort(key = lambda w: w[2], reverse = True)
 
         # 生成文本
@@ -292,7 +293,7 @@ name: %s
 version: "1.0.0"
 sort: by_weight
 ...
-""" % word_dict_name + generator.getWordDictText()
+""" % word_dict_name + generator.getWordDictText(600)
 
     parse_dict_text = """
 # Rime 字典

搞定,重新 build 一下就好了。

目前搭建本地 k8s 集群的方案还挺多的,有官方的 minikube,也有第三方的 k3sk3dKubeKey 等等。简单的测试其实用 k3s 搭个单节点测试就好,但遇到有反亲和规则的 Helm chart 就拉不起来了,所以要么多弄几台机器做分布式集群,要么就用套娃式的虚拟化方案做出多节点来。

现在手头就一台高配物理机,套娃就套娃吧。一开始看 minikube 在 https://github.com/kubernetes 下应该很靠谱才对,但是一个 MySQL 数据库都没拉起来,看报错是文件权限问题。不过 bitnami 的 chart 文档里已经给出了解决方案,启用 volumePermissions.enabled 就可以自动添加一个初始化容器执行 chmod。看起来还行,我把相关子 chart 都启用了这个配置,helm install 也成功了。可这 chart 它不讲武德,竟然在最后的 init 容器里又执行了一次 helm install,比我还能套娃。

这套娃 chart 代码在容器里不是很好改,还是老老实实看怎么让 fsGroup 生效吧。谷歌一下找到 #1990 (comment) 这个 issue,k8s 的 hostPath PV 实现不支持用 fsGroup 配置权限,minikube 和 k3d (rancher/local-path-provisioner#41) 同时中招。

查了好久也没找到合适的方案,看来只能试试非正当手段了。能不能在 hostPath 创建好目录之后,马上把权限改对呢?脏是脏了点,但能抓住老鼠就是好猫。正好 k3d 创建集群时能指定 --volume,直接把宿主机上的目录 mount 进去,然后 for 循环 chmod,总算是顺利把套娃的 helm install 也搞定了。

后记

全都搞定之后才发现 OpenEBS 似乎能解决问题,KubeKey All-in-One 模式也能一键安装,不过懒得折腾了。

后来还发现 minikube 提供的 StorageClass 还不支持多节点集群 (#12165),官方这么惨大家快去欺负(帮忙)。