Skip to content
Merged
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
33 changes: 33 additions & 0 deletions crates/tw-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,30 @@ pub struct Overview {
pub groups: Vec<GroupView>,
pub clients: Vec<ClientView>,
pub listen: ListenView,
/// 三条防线各自的状态(§5.0)。
///
/// **界面要能配它们,而不只是显示。**在此之前这三个字段根本没出现
/// 在这个视图里,于是「脱敏开没开」只能去翻 config.yaml —— 而 §5.0
/// 的整个设计前提是「出厂停在观察态,用户看到证据之后自己决定要不
/// 要切到拦截」,一个切不了的开关让那个设计不成立。
#[serde(default)]
pub security: SecurityView,
}

/// 三条防线。每条三态,而**「拦截」在每条上做的事不一样**,所以动词也
/// 一起给出来 —— 界面上统一叫「拦截」的话,用户点下去并不知道会发生
/// 什么(§5.0)。
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SecurityView {
/// 出站脱敏。拦截态 = 替换成占位符
pub redact: String,
/// 入站审查。拦截态 = 切断响应流
pub inspect_tools: String,
/// 配置面扫描。拦截态 = 告警(它本来就不删东西,§5.3)
pub scan_configs: String,
/// 用户加了几条自定义扫描规则、停用了几条内置的(§5.3)
pub scan_rules_added: usize,
pub scan_rules_disabled: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -367,6 +391,15 @@ pub struct ProviderView {
/// 以为自己改不动它。
#[serde(default)]
pub trust_explicit: bool,
/// 这家上游实际会脱哪几类(§5.1)。
///
/// 给的是**判完的结果**,不是配置里写的那几个字 —— 不写的话官方端点
/// 是空的、其余是那四类默认。界面上要能看出「没写」和「写了空」的
/// 区别,所以另给一个 explicit 标志。
#[serde(default)]
pub redact: Vec<String>,
#[serde(default)]
pub redact_explicit: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
32 changes: 32 additions & 0 deletions crates/tw-config/src/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ impl Mode {
Mode::Enforce => "拦截",
}
}

/// 配置文件里写的那个词。
///
/// `label()` 是给人看的中文,**这个是写回 YAML 用的**。两者必须分开:
/// 界面上把「观察」原样写进 config.yaml 的话,下一次加载会因为
/// 「不是合法取值」整份被拒 —— 而这一层刻意不做静默回落(§5.0),
/// 所以那是一次真的、用户看不懂的启动失败。
pub fn slug(&self) -> &'static str {
match self {
Mode::Off => "off",
Mode::Observe => "observe",
Mode::Enforce => "enforce",
}
}
}

/// 用户自己加的一条扫描规则(§5.3)。
Expand Down Expand Up @@ -126,6 +140,24 @@ impl Security {
mod tests {
use super::*;

/// `slug()` 必须真的能被反序列化回来。
///
/// 界面要能改这三个开关,而它写回 config.yaml 的就是这个字符串。
/// 写错一个词的后果不是「按默认值来」—— 这一层刻意不做静默回落
/// (§5.0:「以为自己开了拦截,其实只在观察」是最糟的状态),所以
/// 整份配置会被拒,表现成一次用户看不懂的启动失败。
///
/// 所以这条不是在测一个 getter,是在把界面和加载器之间那个约定钉住。
#[test]
fn every_slug_round_trips_through_yaml() {
for m in [Mode::Off, Mode::Observe, Mode::Enforce] {
let yaml = format!("redact: {}\n", m.slug());
let back: Security = serde_yaml_ng::from_str(&yaml)
.unwrap_or_else(|e| panic!("slug {:?} 读不回来:{e}", m.slug()));
assert_eq!(back.redact, m, "slug {:?} 解析成了别的档", m.slug());
}
}

#[test]
fn everything_ships_in_observe_mode() {
// **这条测的是一个产品决定,不是一段逻辑。**默认值改成 Off 就
Expand Down
16 changes: 16 additions & 0 deletions crates/tw-control/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ async fn overview(State(s): State<ControlState>) -> Json<tw_api::Overview> {
.label()
.to_string(),
trust_explicit: p.trust.is_some(),
// 和 trust 同一个理由:给判完的结果。不写的时候官方端点是
// 空的、其余是那四类默认,而用户要看的是「这家实际脱哪几
// 类」(§5.1)
redact: p
.effective_redact()
.iter()
.map(|k| k.slug().to_string())
.collect(),
redact_explicit: p.redact.is_some(),
})
.collect(),
routes: engine
Expand Down Expand Up @@ -253,6 +262,13 @@ async fn overview(State(s): State<ControlState>) -> Json<tw_api::Overview> {
max_concurrent: c.max_concurrent,
})
.collect(),
security: tw_api::SecurityView {
redact: cfg.security.redact.slug().to_string(),
inspect_tools: cfg.security.inspect_tools.slug().to_string(),
scan_configs: cfg.security.scan_configs.slug().to_string(),
scan_rules_added: cfg.security.scan_rules.add.len(),
scan_rules_disabled: cfg.security.scan_rules.disable.len(),
},
listen: tw_api::ListenView {
bind: format!("{:?}", cfg.listen.gateway.bind).to_lowercase(),
port: cfg.listen.gateway.port,
Expand Down
Loading