diff --git a/easytier-web/frontend-lib/src/components/Config.vue b/easytier-web/frontend-lib/src/components/Config.vue index d054cc76..745724ee 100644 --- a/easytier-web/frontend-lib/src/components/Config.vue +++ b/easytier-web/frontend-lib/src/components/Config.vue @@ -1,7 +1,7 @@ + + + + + + + {{ t('acl.chain.name') }} + + + + {{ t('acl.rule.description') }} + + + + + + {{ t('acl.rule.enabled') }} + + + + {{ t('acl.chain.type') }} + + + + {{ t('acl.default_action') }} + + + + + + + {{ t('acl.rules') }} + + + + + + + + + + + + + + + + + {{ getProtocolLabel(data.protocol) }} + + + + + + Src + + {{ ip }} + @{{ grp }} + :{{ + data.source_ports.join(',') }} + * + + + + + + + + Dst + + {{ ip }} + @{{ grp }} + :{{ + data.ports.join(',') }} + * + + + + + + + + + + {{ getActionLabel(data.action) }} + + + + + + + + + + + + + + + + diff --git a/easytier-web/frontend-lib/src/components/acl/AclGroupEditor.vue b/easytier-web/frontend-lib/src/components/acl/AclGroupEditor.vue new file mode 100644 index 00000000..bea7ac1b --- /dev/null +++ b/easytier-web/frontend-lib/src/components/acl/AclGroupEditor.vue @@ -0,0 +1,115 @@ + + + + + + + + {{ t('acl.group.declares') }} + {{ t('acl.group.help') }} + + + + + + + + + + + + + + + + + + + + + + + + {{ t('acl.group.members') }} + + + + + + + + {{ t('acl.group.name') }} + + + + {{ t('acl.group.secret') }} + + + + + + + + + + diff --git a/easytier-web/frontend-lib/src/components/acl/AclManager.vue b/easytier-web/frontend-lib/src/components/acl/AclManager.vue new file mode 100644 index 00000000..fa1aabbc --- /dev/null +++ b/easytier-web/frontend-lib/src/components/acl/AclManager.vue @@ -0,0 +1,150 @@ + + + + + + + + + + {{ tab.label }} + + + + + + menu.toggle(event)" /> + + + + + + + + + {{ t('acl.chains') }} + {{ t('acl.help') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easytier-web/frontend-lib/src/components/acl/AclRuleDialog.vue b/easytier-web/frontend-lib/src/components/acl/AclRuleDialog.vue new file mode 100644 index 00000000..e6f08857 --- /dev/null +++ b/easytier-web/frontend-lib/src/components/acl/AclRuleDialog.vue @@ -0,0 +1,150 @@ + + + + + + + + {{ t('acl.rule.name') }} + + + + {{ t('acl.rule.enabled') }} + + + + + + {{ t('acl.rule.description') }} + + + + + + {{ t('acl.rule.action') }} + + + + {{ t('acl.rule.protocol') }} + + + + + + + + {{ t('acl.rule.src_ips') }} + + + + {{ t('acl.rule.dst_ips') }} + + + + + + {{ t('acl.rule.src_ports') }} + + + + {{ t('acl.rule.dst_ports') }} + + + + + + + + + + + {{ t('acl.rule.stateful') }} + + + + + {{ t('acl.rule.rate_limit') }} + + + + {{ t('acl.rule.burst_limit') }} + + + + + + {{ t('acl.rule.src_groups') }} + + + + {{ t('acl.rule.dst_groups') }} + + + + + + + + + + + + diff --git a/easytier-web/frontend-lib/src/locales/cn.yaml b/easytier-web/frontend-lib/src/locales/cn.yaml index b9e03753..ef341c50 100644 --- a/easytier-web/frontend-lib/src/locales/cn.yaml +++ b/easytier-web/frontend-lib/src/locales/cn.yaml @@ -355,6 +355,7 @@ web: delete: 删除 edit: 编辑 refresh: 刷新 + add: 添加 loading: 加载中... error: 错误 success: 成功 @@ -422,3 +423,46 @@ config-server: client: not_running: 无法连接至远程客户端 retry: 重试 + +acl: + title: 访问控制 + help: 访问控制列表,用于限制节点间的通信。 + enabled: 启用 ACL + default_action: 默认动作 + chains: 规则链 + inbound: 入站 + outbound: 出站 + forward: 转发 + rules: 规则 + add_rule: 添加规则 + edit_rule: 编辑规则 + rule: + name: 规则名称 + description: 描述 + enabled: 启用 + protocol: 协议 + action: 动作 + src_ips: 来源 IP + dst_ips: 目的 IP + src_ports: 来源端口 + dst_ports: 目的端口 + rate_limit: 速率限制 (pps) + burst_limit: 爆发限制 + stateful: 状态追踪 + src_groups: 来源组 + dst_groups: 目的组 + groups: 组管理 + group: + declares: 声明组 + members: 加入组 + name: 组名 + secret: 密钥 + help: 在此处定义网络中的组身份,以便在规则中使用。 + any: 任意 + allow: 允许 + drop: 丢弃 + delete_chain_confirm: 确定要删除此规则链及其所有规则吗? + chain: + name: 名称 + type: 类型 + match: 匹配 diff --git a/easytier-web/frontend-lib/src/locales/en.yaml b/easytier-web/frontend-lib/src/locales/en.yaml index c614d50c..3d8c847a 100644 --- a/easytier-web/frontend-lib/src/locales/en.yaml +++ b/easytier-web/frontend-lib/src/locales/en.yaml @@ -355,6 +355,7 @@ web: delete: Delete edit: Edit refresh: Refresh + add: Add loading: Loading... error: Error success: Success @@ -422,3 +423,46 @@ config-server: client: not_running: Unable to connect to remote client. retry: Retry + +acl: + title: Access Control (ACL) + help: Access control list to restrict communication between nodes. + enabled: Enable ACL + default_action: Default Action + chains: Rule Chains + inbound: Inbound + outbound: Outbound + forward: Forward + rules: Rules + add_rule: Add Rule + edit_rule: Edit Rule + rule: + name: Rule Name + description: Description + enabled: Enabled + protocol: Protocol + action: Action + src_ips: Source IPs + dst_ips: Destination IPs + src_ports: Source Ports + dst_ports: Destination Ports + rate_limit: Rate Limit (pps) + burst_limit: Burst Limit + stateful: Stateful + src_groups: Source Groups + dst_groups: Destination Groups + groups: Groups + group: + declares: Declared Groups + members: Node Memberships + name: Group Name + secret: Group Secret + help: Define group identities in the network to use them in rules. + any: Any + allow: Allow + drop: Drop + delete_chain_confirm: Are you sure you want to delete this rule chain and all its rules? + chain: + name: Name + type: Type + match: Match diff --git a/easytier-web/frontend-lib/src/types/network.ts b/easytier-web/frontend-lib/src/types/network.ts index 4b3517eb..45247c3e 100644 --- a/easytier-web/frontend-lib/src/types/network.ts +++ b/easytier-web/frontend-lib/src/types/network.ts @@ -14,6 +14,74 @@ export interface SecureModeConfig { local_public_key?: string } +export enum AclProtocol { + Unspecified = 0, + TCP = 1, + UDP = 2, + ICMP = 3, + ICMPv6 = 4, + Any = 5, +} + +export enum AclAction { + Noop = 0, + Allow = 1, + Drop = 2, +} + +export enum AclChainType { + UnspecifiedChain = 0, + Inbound = 1, + Outbound = 2, + Forward = 3, +} + +export interface AclRule { + name: string + description: string + priority: number + enabled: boolean + protocol: AclProtocol + ports: string[] + source_ips: string[] + destination_ips: string[] + source_ports: string[] + action: AclAction + rate_limit: number + burst_limit: number + stateful: boolean + source_groups: string[] + destination_groups: string[] +} + +export interface AclChain { + name: string + chain_type: AclChainType + description: string + enabled: boolean + rules: AclRule[] + default_action: AclAction +} + +export interface GroupIdentity { + group_name: string + group_secret: string +} + +export interface GroupInfo { + declares: GroupIdentity[] + members: string[] +} + +export interface AclV1 { + chains: AclChain[] + group?: GroupInfo +} + +export interface Acl { + acl_v1?: AclV1 +} + export interface NetworkConfig { instance_id: string @@ -85,6 +153,7 @@ export interface NetworkConfig { enable_private_mode?: boolean port_forwards: PortForwardConfig[] + acl?: Acl } export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { @@ -152,6 +221,15 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { enable_magic_dns: false, enable_private_mode: false, port_forwards: [], + acl: { + acl_v1: { + group: { + declares: [], + members: [], + }, + chains: [], + }, + }, } } diff --git a/easytier/src/launcher.rs b/easytier/src/launcher.rs index a5dc001f..3ae57af4 100644 --- a/easytier/src/launcher.rs +++ b/easytier/src/launcher.rs @@ -825,6 +825,12 @@ impl NetworkConfig { flags.encryption_algorithm = encryption_algorithm; } + if let Some(acl) = self.acl.as_ref() + && !acl.is_empty() + { + cfg.set_acl(Some(acl.clone())); + } + if let Some(data_compress_algo) = self.data_compress_algo { if data_compress_algo < 1 { flags.data_compress_algo = 1; @@ -964,6 +970,8 @@ impl NetworkConfig { (flags.instance_recv_bps_limit != u64::MAX).then_some(flags.instance_recv_bps_limit); result.enable_private_mode = Some(flags.private_mode); + result.acl = config.get_acl(); + if flags.relay_network_whitelist == "*" { result.enable_relay_network_whitelist = Some(false); } else { diff --git a/easytier/src/proto/acl.rs b/easytier/src/proto/acl.rs index 6948ba3f..03d11866 100644 --- a/easytier/src/proto/acl.rs +++ b/easytier/src/proto/acl.rs @@ -2,6 +2,26 @@ use std::fmt::Display; include!(concat!(env!("OUT_DIR"), "/acl.rs")); +impl Acl { + pub fn is_empty(&self) -> bool { + self.acl_v1.as_ref().map(|v1| v1.is_empty()).unwrap_or(true) + } +} + +impl AclV1 { + pub fn is_empty(&self) -> bool { + let has_chains = !self.chains.is_empty(); + let has_groups = self.group.as_ref().map(|g| !g.is_empty()).unwrap_or(false); + !has_chains && !has_groups + } +} + +impl GroupInfo { + pub fn is_empty(&self) -> bool { + self.declares.is_empty() && self.members.is_empty() + } +} + impl Display for ConnTrackEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let src = self diff --git a/easytier/src/proto/api_manage.proto b/easytier/src/proto/api_manage.proto index b79ff5d7..0641672f 100644 --- a/easytier/src/proto/api_manage.proto +++ b/easytier/src/proto/api_manage.proto @@ -3,6 +3,7 @@ syntax = "proto3"; import "common.proto"; import "peer_rpc.proto"; import "api_instance.proto"; +import "acl.proto"; package api.manage; @@ -83,7 +84,7 @@ message NetworkConfig { optional bool disable_tcp_hole_punching = 54; common.SecureModeConfig secure_mode = 55; - reserved 56; + optional acl.Acl acl = 56; optional string credential_file = 57; optional bool lazy_p2p = 58; optional bool need_p2p = 59;
{{ t('acl.help') }}