Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,938 changes: 2,938 additions & 0 deletions app/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ aes = "0.8.4"
reqwest = {version = "0.11.27", features= ["rustls-tls"] }

[features]
default = ["etcd-client-tls"]
default = ["etcd-client-tls-openssl"]

# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
Expand Down
61 changes: 54 additions & 7 deletions app/src-tauri/src/etcd/etcd_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::transport::user::{ReadableKeys, SerializablePermission, SerializableU
use crate::utils::k8s_formatter;
use etcd_client::{
AlarmAction, AlarmType, Client, CompactionOptions, ConnectOptions, Error, GetOptions,
GetResponse, Identity, LeaseGrantOptions, LeaseTimeToLiveOptions, PermissionType, PutOptions,
GetResponse, LeaseGrantOptions, LeaseTimeToLiveOptions, PermissionType, PutOptions,
RoleRevokePermissionOptions, SortOrder, SortTarget, WatchOptions, WatchStream, Watcher,
};
use log::{debug, error, info, warn};
Expand Down Expand Up @@ -85,11 +85,15 @@ impl EtcdConnector {
};

if let Some(identity) = tls.identity {
tls_option =
tls_option.identity(Identity::from_pem(identity.cert, identity.key));
let cert = filter_pem(&identity.cert, "CERTIFICATE");
let key = filter_pem(&identity.key, "PRIVATE KEY");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为什么需要手动过滤掉 PEM 中的标识块?底层工具可以自动处理这些标识,并且去掉了key的标识信息可能会导致无法读取对应的算法,例如:

ssl工具根据标识块信息得知密钥算法为RSA,需要以 PKCS#1 格式解析其中内容

-----BEGIN RSA PRIVATE KEY-----
xxx
-----END RSA PRIVATE KEY-----

同理,你在filter_pem 函数中兼容的 EC PRIVATE KEY 块中EC也可能是有用信息,另外还有 Ed25519 等。

建议在交生成 identity 之前不要手动处理 PEM 文件内容,这可能存在兼容性问题

if cert.is_empty() || key.is_empty() {
warn!("Identity cert or key is empty after filtering. Original cert len: {}, key len: {}", identity.cert.len(), identity.key.len());
}
tls_option = tls_option.identity(Identity::from_pem(cert, key));
};

option = option.with_tls(tls_option)
option = option.with_tls(tls_option);
}

#[cfg(feature = "etcd-client-tls-openssl")]
Expand Down Expand Up @@ -121,8 +125,9 @@ impl EtcdConnector {
};

if let Some(identity) = tls.identity {
openssl_config = openssl_config
.client_cert_pem_and_key(identity.cert.as_slice(), identity.key.as_slice());
let cert = filter_pem(&identity.cert, "CERTIFICATE");
let key = filter_pem(&identity.key, "PRIVATE KEY");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里也和前面 rust-tls 处理一样,PEM文件交由下层工具处理,除非下层无法正确处理PEM文件

openssl_config = openssl_config.client_cert_pem_and_key(&cert, &key);
};

option = option.with_openssl_tls(openssl_config);
Expand Down Expand Up @@ -150,7 +155,19 @@ impl EtcdConnector {

let address = format!("{}:{}", host, port);
info!("Connect to etcd server: {}", address);
let client = Client::connect([address], Some(option)).await?;

let client_res = Client::connect([address], Some(option)).await;
let client = match client_res {
Ok(c) => c,
Err(e) => {
error!("!!! ETCD CONNECTION FAILED !!!");
error!("Error detail: {:?}", e);
if let etcd_client::Error::TransportError(ref msg) = e {
error!("Transport error message: {}", msg);
}
return Err(LogicError::EtcdClientError(e));
}
};

Ok(EtcdConnector {
namespace,
Expand Down Expand Up @@ -1217,6 +1234,36 @@ fn key_next(key: &mut Vec<u8>) {
key[len - 1] += 1
}
}

/// 过滤 PEM 内容,只保留指定标签的数据块。
/// 例如:label 为 "CERTIFICATE" 时,只保留 BEGIN/END CERTIFICATE 之间的部分。
fn filter_pem(data: &[u8], label: &str) -> Vec<u8> {
let content = String::from_utf8_lossy(data);
let mut filtered = String::new();
let begin_tag = format!("-----BEGIN {}", label);
let end_tag = format!("-----END {}", label);

let mut in_block = false;
for line in content.lines() {
if line.contains(&begin_tag) {
in_block = true;
}
if in_block {
filtered.push_str(line);
filtered.push('\n');
}
if line.contains(&end_tag) {
in_block = false;
}
}
Comment on lines +1246 to +1258

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里存在一个可能的漏洞,当PEM中存在多个密钥段时,可能会读取到错误的密钥内容,例如这样的文件:

-----BEGIN PRIVATE KEY-----
xxx
-----END PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
xxx
-----END PUBLIC KEY-----

-----BEGIN CERTIFICATE-----
xxx
-----END CERTIFICATE-----

-----BEGIN PRIVATE KEY-----
xxx
-----END PRIVATE KEY-----

当然如果此函数可以移除,这个问题可以忽略


// 如果没有找到完全匹配的 label,尝试模糊匹配(例如 PRIVATE KEY 匹配 EC PRIVATE KEY)
if filtered.is_empty() && label == "PRIVATE KEY" {
return filter_pem(data, "EC PRIVATE KEY");
}

filtered.into_bytes()
}
pub struct SnapshotTask {
pub name: String,
pub folder: String,
Expand Down
8 changes: 8 additions & 0 deletions app/src-tauri/src/transport/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub struct SettingConfig {
/// KV保存之前是否进行差异确认
#[serde(default = "default_kv_confirm_diff_before_save")]
pub kv_confirm_diff_before_save: bool,
/// KV打开时是否自动格式化
#[serde(default = "default_kv_auto_format")]
pub kv_auto_format: bool,
/// KV树状搜索是否包含文件夹
#[serde(default = "default_kv_tree_search_with_folder")]
pub kv_tree_search_with_folder: bool,
Expand Down Expand Up @@ -89,6 +92,10 @@ fn default_kv_confirm_diff_before_save() -> bool {
true
}

fn default_kv_auto_format() -> bool {
true
}

fn default_kv_tree_search_with_folder() -> bool {
true
}
Expand Down Expand Up @@ -135,6 +142,7 @@ impl Default for SettingConfig {
kv_path_splitter: default_kv_path_splitter(),
kv_check_format_before_save: default_kv_check_format_before_save(),
kv_confirm_diff_before_save: default_kv_confirm_diff_before_save(),
kv_auto_format: default_kv_auto_format(),
kv_tree_search_with_folder: default_kv_tree_search_with_folder(),
kv_search_next_dir_limit: default_kv_search_next_dir_limit(),
kv_dir_rename_keys_limit: default_kv_dir_rename_keys_limit(),
Expand Down
3 changes: 3 additions & 0 deletions app/src/common/transport/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface SettingConfig {
kvPathSplitter: string,
// KV保存之前是否检查格式
kvCheckFormatBeforeSave: boolean,
// KV内容显示时是否进行自动格式化
kvAutoFormat: boolean,
// KV保存之前是否进行差异确认
kvConfirmDiffBeforeSave: boolean,
kvTreeSearchWithFolder: boolean,
Expand Down Expand Up @@ -59,6 +61,7 @@ export const DEFAULT_SETTING_CONFIG: SettingConfig = {
editorLightTheme: 'smoothy',
kvPathSplitter: '/',
kvCheckFormatBeforeSave: true,
kvAutoFormat: true,
kvConfirmDiffBeforeSave: true,
kvTreeSearchWithFolder: true,
kvSearchNextDirLimit: 100,
Expand Down
1 change: 1 addition & 0 deletions app/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export type EditorConfig = {
height: string | 'auto'
language: EditorHighlightLanguage
fontSize: string,
autoFormat?: boolean,
}

export type LogicErrorInfo = {
Expand Down
64 changes: 39 additions & 25 deletions app/src/components/editor/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -177,27 +177,6 @@ const size = computed(() => {
return _byteTextFormat(_encodeStringToBytes(content.value).length)
})

watch(
() => props.value,
(_code: string) => {
content.value = formatData(_code, 'text', props.config!.language == 'blob' ? 'blob' : 'text')
}
)

watch(
() => props.config!.language,
(newLang, oldLang) => {
if (newLang != oldLang) {
content.value = formatData(
content.value,
oldLang == 'blob' ? 'blob' : 'text',
newLang == 'blob' ? 'blob' : 'text',
)
}
}, {
deep: true
}
)

const handleReady = ({view}: any) => {
const cm = view as EditorView
Expand All @@ -212,13 +191,17 @@ const handleReady = ({view}: any) => {
});
}

const onChanged = (data: string) => {
const _onChanged = (data: string, silent: boolean = false) => {
emits('change', {
data,
modified: data !== props.value
modified: silent ? false : data !== props.value
})
}

const onChanged = (data: string) => {
_onChanged(data, false)
}

const onKeyDown = (event: KeyboardEvent) => {
if (event.key == 's' && (event.ctrlKey || event.metaKey)) {
event.preventDefault()
Expand All @@ -234,21 +217,25 @@ const changeLanguage = (lang: EditorHighlightLanguage) => {
}

// 对当前内容进行格式化
const formatContent = () => {
const _formatContent = (silent: boolean = false) => {
showLanguageSelection.value = false
tryFormatContent().then(newContent => {
if (newContent) {
let oldContent = content.value
content.value = newContent
if (newContent != oldContent) {
onChanged(newContent)
_onChanged(newContent, silent)
}
}
}).catch(e => {
console.debug(e)
})
}

const formatContent = () => {
_formatContent(false)
}

// 尝试格式化,但并不会实际修改当前值
const tryFormatContent = (): Promise<string | undefined> => {
let language = props.config?.language
Expand Down Expand Up @@ -315,6 +302,33 @@ const readDataString = (): string => {
return content.value
}

watch(
() => props.value,
(_code: string) => {
content.value = formatData(_code, 'text', props.config!.language == 'blob' ? 'blob' : 'text')
if (props.config.autoFormat) {
_formatContent(true)
}
}, {
immediate: true
}
)

watch(
() => props.config!.language,
(newLang, oldLang) => {
if (newLang != oldLang) {
content.value = formatData(
content.value,
oldLang == 'blob' ? 'blob' : 'text',
newLang == 'blob' ? 'blob' : 'text',
)
}
}, {
deep: true
}
)

defineExpose({
readDataBytes,
tryFormatContent,
Expand Down
2 changes: 2 additions & 0 deletions app/src/language/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ export default {
checkFormatBeforeSaveDesc: "Before saving the currently edited key each time, check whether the value format is correct.",
confirmDiffBeforeSave: "Confirm diff before saving",
confirmDiffBeforeSaveDesc: "Compare and confirm modifications before saving",
autoFormat: "Auto format on key open",
autoFormatDesc: "Whether to automatically perform beautification formatting when opening KV content in the editor if the content is in a format such as JSON/YAML/XML.",
editorTheme: "Editor theme",
editorThemeDesc: "Set the Key-Value editor personalized theme.",
editorLightTheme: "Light theme",
Expand Down
2 changes: 2 additions & 0 deletions app/src/language/locales/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ export default {
checkFormatBeforeSaveDesc: "在每次保存键之前检查格式是否正确",
confirmDiffBeforeSave: "保存前确认变化",
confirmDiffBeforeSaveDesc: "在保存之前进行修改对比确认",
autoFormat: "打开时自动格式化内容",
autoFormatDesc: "在编辑器中打开 KV 内容时,如果内容为 JSON/YAML/XML 等可格式化内容,是否自动执行美化格式操作",
editorTheme: "编辑器主题",
editorThemeDesc: "设置编辑器主题颜色",
editorLightTheme: "亮色主题",
Expand Down
6 changes: 4 additions & 2 deletions app/src/pages/main/Keys.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,13 @@ const defaultEditorConfig: EditorConfig = {
}

const editorConfig = reactive<EditorConfig>({
...defaultEditorConfig
...defaultEditorConfig,
autoFormat: settings.value.kvAutoFormat
})

const newKeyEditorConfig = reactive<EditorConfig>({
...defaultEditorConfig
...defaultEditorConfig,
autoFormat: settings.value.kvAutoFormat
})

const loadingStore = reactive({
Expand Down
20 changes: 20 additions & 0 deletions app/src/pages/setting/AppSetting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,26 @@ const checkUpdate = () => {

<v-divider class="mt-5 mb-5"></v-divider>

<v-layout>
<div>
<div class="form-label text-high-emphasis">{{ t('setting.autoFormat') }}</div>
<div class="v-messages">{{ t('setting.autoFormatDesc') }}</div>
</div>
<v-spacer></v-spacer>
<div>
<v-switch
v-model="settingForm.kvAutoFormat"
inset
density="compact"
color="primary"
hide-details
true-icon="mdi-check"
/>
</div>
</v-layout>

<v-divider class="mt-5 mb-5"></v-divider>

<p class="mt-5 user-select-none">{{ t('setting.editorTheme') }}</p>
<p class="v-messages">{{ t('setting.editorThemeDesc') }}</p>
<v-sheet class="mt-5 form-area">
Expand Down