mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
Implement ACL (#1140)
1. get acl stats
```
./easytier-cli acl stats
AclStats:
Global:
CacheHits: 4
CacheMaxSize: 10000
CacheSize: 5
DefaultAllows: 3
InboundPacketsAllowed: 2
InboundPacketsTotal: 2
OutboundPacketsAllowed: 7
OutboundPacketsTotal: 7
PacketsAllowed: 9
PacketsTotal: 9
RuleMatches: 2
ConnTrack:
[src: 10.14.11.1:57444, dst: 10.14.11.2:1000, proto: Tcp, state: New, pkts: 1, bytes: 60, created: 2025-07-24 10:13:39 +08:00, last_seen: 2025-07-24 10:13:39 +08:00]
Rules:
[name: 'tcp_whitelist', prio: 1000, action: Allow, enabled: true, proto: Tcp, ports: ["1000"], src_ports: [], src_ips: [], dst_ips: [], stateful: true, rate: 0, burst: 0] [pkts: 2, bytes: 120]
```
2. use tcp/udp whitelist to block unexpected traffic.
`sudo ./easytier-core -d --tcp-whitelist 1000`
3. use complete acl ability with config file:
```
[[acl.acl_v1.chains]]
name = "inbound_whitelist"
chain_type = 1
description = "Auto-generated inbound whitelist from CLI"
enabled = true
default_action = 2
[[acl.acl_v1.chains.rules]]
name = "tcp_whitelist"
description = "Auto-generated TCP whitelist rule"
priority = 1000
enabled = true
protocol = 1
ports = ["1000"]
source_ips = []
destination_ips = []
source_ports = []
action = 1
rate_limit = 0
burst_limit = 0
stateful = true
```
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
package acl;
|
||||
|
||||
// Enhanced protocol enum with more granular options
|
||||
enum Protocol {
|
||||
Unspecified = 0;
|
||||
TCP = 1;
|
||||
UDP = 2;
|
||||
ICMP = 3;
|
||||
ICMPv6 = 4;
|
||||
Any = 5;
|
||||
}
|
||||
|
||||
enum Action {
|
||||
Noop = 0;
|
||||
Allow = 1;
|
||||
Drop = 2; // Silent drop (no response)
|
||||
}
|
||||
|
||||
enum ChainType {
|
||||
UnspecifiedChain = 0;
|
||||
// send to this node
|
||||
Inbound = 1;
|
||||
// send from this node
|
||||
Outbound = 2;
|
||||
// subnet proxy
|
||||
Forward = 3;
|
||||
}
|
||||
|
||||
// Time-based access control
|
||||
message TimeWindow {
|
||||
// Days of week: 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
repeated uint32 days_of_week = 1;
|
||||
// Time in minutes from midnight (0-1439)
|
||||
uint32 start_time = 2;
|
||||
uint32 end_time = 3;
|
||||
// Timezone offset in minutes from UTC
|
||||
int32 timezone_offset = 4;
|
||||
}
|
||||
|
||||
// Enhanced rule with priority and metadata
|
||||
message Rule {
|
||||
// Rule identification and metadata
|
||||
string name = 1; // Human-readable rule name
|
||||
string description = 2; // Rule description
|
||||
uint32 priority = 3; // Higher number = higher priority (0-65535)
|
||||
bool enabled = 4; // Rule enabled/disabled state
|
||||
|
||||
// Core matching criteria
|
||||
Protocol protocol = 5;
|
||||
repeated string ports = 6;
|
||||
repeated string source_ips = 7; // Source IP ranges
|
||||
repeated string destination_ips = 8; // Destination IP ranges
|
||||
|
||||
// Enhanced matching criteria
|
||||
repeated string source_ports = 9; // Source port range
|
||||
|
||||
// Action and logging
|
||||
Action action = 10;
|
||||
|
||||
// Rate limiting (packets per second)
|
||||
uint32 rate_limit = 11; // 0 = no limit
|
||||
uint32 burst_limit = 12; // Burst allowance
|
||||
|
||||
// Connection tracking
|
||||
bool stateful = 13; // Enable connection tracking
|
||||
}
|
||||
|
||||
// Rule chain with metadata and optimization hints
|
||||
message Chain {
|
||||
// Chain identification
|
||||
string name = 1; // Human-readable chain name
|
||||
ChainType chain_type = 2;
|
||||
string description = 3; // Chain description
|
||||
bool enabled = 4; // Chain enabled/disabled state
|
||||
|
||||
// Rules in priority order (highest priority first)
|
||||
repeated Rule rules = 5;
|
||||
|
||||
// Default action when no rules match
|
||||
Action default_action = 6;
|
||||
}
|
||||
|
||||
message AclV1 { repeated Chain chains = 1; }
|
||||
|
||||
enum ConnState {
|
||||
New = 0;
|
||||
Established = 1;
|
||||
Related = 2;
|
||||
Invalid = 3;
|
||||
}
|
||||
|
||||
// Connection tracking entry for stateful ACLs
|
||||
message ConnTrackEntry {
|
||||
common.SocketAddr src_addr = 1;
|
||||
common.SocketAddr dst_addr = 2;
|
||||
Protocol protocol = 3; // IP protocol number (e.g., 6 = TCP, 17 = UDP)
|
||||
ConnState state = 4;
|
||||
uint64 created_at = 5; // Unix timestamp (seconds)
|
||||
uint64 last_seen = 6; // Unix timestamp (seconds)
|
||||
uint64 packet_count = 7;
|
||||
uint64 byte_count = 8;
|
||||
}
|
||||
|
||||
// Top-level ACL configuration
|
||||
message Acl {
|
||||
AclV1 acl_v1 = 2;
|
||||
}
|
||||
|
||||
message StatItem {
|
||||
uint64 packet_count = 1;
|
||||
uint64 byte_count = 2;
|
||||
}
|
||||
|
||||
message RuleStats {
|
||||
Rule rule = 1;
|
||||
StatItem stat = 2;
|
||||
}
|
||||
|
||||
message AclStats {
|
||||
repeated RuleStats rules = 1;
|
||||
repeated ConnTrackEntry conn_track = 2;
|
||||
map<string, uint64> global = 3;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/acl.rs"));
|
||||
|
||||
impl Display for ConnTrackEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let src = self
|
||||
.src_addr
|
||||
.as_ref()
|
||||
.map(|a| a.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let dst = self
|
||||
.dst_addr
|
||||
.as_ref()
|
||||
.map(|a| a.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let last_seen = chrono::DateTime::<chrono::Utc>::from_timestamp(self.last_seen as i64, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Local);
|
||||
let created_at = chrono::DateTime::<chrono::Utc>::from_timestamp(self.created_at as i64, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Local);
|
||||
write!(
|
||||
f,
|
||||
"[src: {}, dst: {}, proto: {:?}, state: {:?}, pkts: {}, bytes: {}, created: {}, last_seen: {}]",
|
||||
src,
|
||||
dst,
|
||||
Protocol::try_from(self.protocol).unwrap_or(Protocol::Unspecified),
|
||||
ConnState::try_from(self.state).unwrap_or(ConnState::Invalid),
|
||||
self.packet_count,
|
||||
self.byte_count,
|
||||
created_at,
|
||||
last_seen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rule {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[name: '{}', prio: {}, action: {:?}, enabled: {}, proto: {:?}, ports: {:?}, src_ports: {:?}, src_ips: {:?}, dst_ips: {:?}, stateful: {}, rate: {}, burst: {}]",
|
||||
self.name,
|
||||
self.priority,
|
||||
Action::try_from(self.action).unwrap_or(Action::Noop),
|
||||
self.enabled,
|
||||
Protocol::try_from(self.protocol).unwrap_or(Protocol::Unspecified),
|
||||
self.ports,
|
||||
self.source_ports,
|
||||
self.source_ips,
|
||||
self.destination_ips,
|
||||
self.stateful,
|
||||
self.rate_limit,
|
||||
self.burst_limit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StatItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[pkts: {}, bytes: {}]",
|
||||
self.packet_count, self.byte_count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AclStats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "AclStats:")?;
|
||||
writeln!(f, " Global:")?;
|
||||
for (k, v) in &self.global {
|
||||
writeln!(f, " {}: {}", k, v)?;
|
||||
}
|
||||
writeln!(f, " ConnTrack:")?;
|
||||
for entry in &self.conn_track {
|
||||
writeln!(f, " {}", entry)?;
|
||||
}
|
||||
writeln!(f, " Rules:")?;
|
||||
for rule_stat in &self.rules {
|
||||
if let Some(rule) = &rule_stat.rule {
|
||||
write!(f, " {} ", rule)?;
|
||||
} else {
|
||||
write!(f, " <default/none> ")?;
|
||||
}
|
||||
if let Some(stat) = &rule_stat.stat {
|
||||
writeln!(f, "{}", stat)?;
|
||||
} else {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "peer_rpc.proto";
|
||||
import "acl.proto";
|
||||
|
||||
package cli;
|
||||
|
||||
@@ -251,3 +252,13 @@ service TcpProxyRpc {
|
||||
rpc ListTcpProxyEntry(ListTcpProxyEntryRequest)
|
||||
returns (ListTcpProxyEntryResponse);
|
||||
}
|
||||
|
||||
message GetAclStatsRequest {}
|
||||
|
||||
message GetAclStatsResponse {
|
||||
acl.AclStats acl_stats = 1;
|
||||
}
|
||||
|
||||
service AclManageRpc {
|
||||
rpc GetAclStats(GetAclStatsRequest) returns (GetAclStatsResponse);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ message FlagsInConfig {
|
||||
bool disable_p2p = 11;
|
||||
bool relay_all_peer_rpc = 12;
|
||||
bool disable_udp_hole_punching = 13;
|
||||
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345 instead
|
||||
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345
|
||||
// instead
|
||||
bool multi_thread = 15;
|
||||
CompressionAlgoPb data_compress_algo = 16;
|
||||
bool bind_device = 17;
|
||||
@@ -144,6 +145,13 @@ message Ipv6Inet {
|
||||
uint32 network_length = 2;
|
||||
}
|
||||
|
||||
message IpInet {
|
||||
oneof ip {
|
||||
Ipv4Inet ipv4 = 1;
|
||||
Ipv6Inet ipv6 = 2;
|
||||
};
|
||||
}
|
||||
|
||||
message Url { string url = 1; }
|
||||
|
||||
message SocketAddr {
|
||||
@@ -173,7 +181,7 @@ message PeerFeatureFlag {
|
||||
bool is_public_server = 1;
|
||||
bool avoid_relay_data = 2;
|
||||
bool kcp_input = 3;
|
||||
bool no_relay_kcp = 4;
|
||||
bool no_relay_kcp = 4;
|
||||
}
|
||||
|
||||
enum SocketType {
|
||||
@@ -182,17 +190,17 @@ enum SocketType {
|
||||
}
|
||||
|
||||
message PortForwardConfigPb {
|
||||
SocketAddr bind_addr = 1;
|
||||
SocketAddr dst_addr = 2;
|
||||
SocketType socket_type = 3;
|
||||
SocketAddr bind_addr = 1;
|
||||
SocketAddr dst_addr = 2;
|
||||
SocketType socket_type = 3;
|
||||
}
|
||||
|
||||
message ProxyDstInfo {
|
||||
SocketAddr dst_addr = 1;
|
||||
}
|
||||
message ProxyDstInfo { SocketAddr dst_addr = 1; }
|
||||
|
||||
message LimiterConfig {
|
||||
optional uint64 burst_rate = 1; // default 1 means no burst (capacity is same with bps)
|
||||
optional uint64 burst_rate =
|
||||
1; // default 1 means no burst (capacity is same with bps)
|
||||
optional uint64 bps = 2; // default 0 means no limit (unit is B/s)
|
||||
optional uint64 fill_duration_ms = 3; // default 10ms, the period to fill the bucket
|
||||
optional uint64 fill_duration_ms =
|
||||
3; // default 10ms, the period to fill the bucket
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
@@ -166,6 +169,43 @@ impl FromStr for Ipv6Inet {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cidr::IpInet> for IpInet {
|
||||
fn from(value: cidr::IpInet) -> Self {
|
||||
match value {
|
||||
cidr::IpInet::V4(v4) => IpInet {
|
||||
ip: Some(ip_inet::Ip::Ipv4(Ipv4Inet::from(v4))),
|
||||
},
|
||||
cidr::IpInet::V6(v6) => IpInet {
|
||||
ip: Some(ip_inet::Ip::Ipv6(Ipv6Inet::from(v6))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpInet> for cidr::IpInet {
|
||||
fn from(value: IpInet) -> Self {
|
||||
match value.ip {
|
||||
Some(ip_inet::Ip::Ipv4(v4)) => cidr::IpInet::V4(v4.into()),
|
||||
Some(ip_inet::Ip::Ipv6(v6)) => cidr::IpInet::V6(v6.into()),
|
||||
None => panic!("IpInet is None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpInet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", cidr::IpInet::from(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IpInet {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(IpInet::from(cidr::IpInet::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for Url {
|
||||
fn from(value: url::Url) -> Self {
|
||||
Url {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod rpc_impl;
|
||||
pub mod rpc_types;
|
||||
|
||||
pub mod acl;
|
||||
pub mod cli;
|
||||
pub mod common;
|
||||
pub mod error;
|
||||
|
||||
Reference in New Issue
Block a user